Integrate TableStateContext for enhanced table state management
- Introduced TableStateContext to manage table filters and sorting, allowing for persistence across sessions and URL parameters. - Updated ObjectTable and DashboardBreadcrumb components to utilize the new context for improved filter handling and user experience. - Refactored DashboardLayout to include TableStateProvider, ensuring consistent state management across the dashboard components.
This commit is contained in:
parent
84b7197bb9
commit
bc465f7202
@ -12,6 +12,7 @@ import DashboardBreadcrumb from './common/DashboardBreadcrumb'
|
|||||||
import DeveloperSidebar from './Developer/DeveloperSidebar'
|
import DeveloperSidebar from './Developer/DeveloperSidebar'
|
||||||
import { useThemeContext } from './context/ThemeContext'
|
import { useThemeContext } from './context/ThemeContext'
|
||||||
import { MessageProvider } from './context/MessageContext'
|
import { MessageProvider } from './context/MessageContext'
|
||||||
|
import { TableStateProvider } from './context/TableStateContext'
|
||||||
|
|
||||||
const { Content } = Layout
|
const { Content } = Layout
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ const DashboardLayout = ({ children }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageProvider>
|
<MessageProvider>
|
||||||
|
<TableStateProvider>
|
||||||
<Layout
|
<Layout
|
||||||
style={{ height: 'var(--unit-100vh)' }}
|
style={{ height: 'var(--unit-100vh)' }}
|
||||||
className={isDarkMode ? 'dark-mode' : 'light-mode'}
|
className={isDarkMode ? 'dark-mode' : 'light-mode'}
|
||||||
@ -62,6 +64,7 @@ const DashboardLayout = ({ children }) => {
|
|||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
</TableStateProvider>
|
||||||
</MessageProvider>
|
</MessageProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,9 @@ import { Breadcrumb, Button, Flex, Space } from 'antd'
|
|||||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import ArrowLeftIcon from '../../Icons/ArrowLeftIcon'
|
import ArrowLeftIcon from '../../Icons/ArrowLeftIcon'
|
||||||
import ArrowRightIcon from '../../Icons/ArrowRightIcon'
|
import ArrowRightIcon from '../../Icons/ArrowRightIcon'
|
||||||
|
import FilterIcon from '../../Icons/FilterIcon'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import { useTableState } from '../context/TableStateContext'
|
||||||
|
|
||||||
const breadcrumbNameMap = {
|
const breadcrumbNameMap = {
|
||||||
production: 'Production',
|
production: 'Production',
|
||||||
@ -26,6 +28,7 @@ const mainSections = ['production', 'inventory', 'management', 'developer']
|
|||||||
const DashboardBreadcrumb = () => {
|
const DashboardBreadcrumb = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { hasPageFilter, hasStoredFilter } = useTableState()
|
||||||
const pathSnippets = location.pathname.split('/').filter((i) => i)
|
const pathSnippets = location.pathname.split('/').filter((i) => i)
|
||||||
|
|
||||||
function segmentToModel(segment) {
|
function segmentToModel(segment) {
|
||||||
@ -48,6 +51,9 @@ const DashboardBreadcrumb = () => {
|
|||||||
const model = segmentToModel(segment)
|
const model = segmentToModel(segment)
|
||||||
const modelLabelPlural = model?.label ? `${model.label}s` : null
|
const modelLabelPlural = model?.label ? `${model.label}s` : null
|
||||||
const name = breadcrumbNameMap[segment] || modelLabelPlural || segment
|
const name = breadcrumbNameMap[segment] || modelLabelPlural || segment
|
||||||
|
const showFilterIcon =
|
||||||
|
hasPageFilter(url) ||
|
||||||
|
(model?.name && model.name !== 'unknown' && hasStoredFilter(model.name))
|
||||||
if (isMainSection) {
|
if (isMainSection) {
|
||||||
return {
|
return {
|
||||||
title: (
|
title: (
|
||||||
@ -61,8 +67,19 @@ const DashboardBreadcrumb = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
title: (
|
title: (
|
||||||
<Link to={url} style={{ padding: '0 12px' }}>
|
<Link
|
||||||
|
to={url}
|
||||||
|
style={{
|
||||||
|
padding: '0 12px',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6
|
||||||
|
}}
|
||||||
|
>
|
||||||
{name}
|
{name}
|
||||||
|
{showFilterIcon && (
|
||||||
|
<FilterIcon style={{ fontSize: 12, opacity: 0.65 }} />
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
key: url
|
key: url
|
||||||
|
|||||||
@ -39,13 +39,14 @@ import ObjectCard from './ObjectCard'
|
|||||||
import FilterSidebar from './FilterSidebar'
|
import FilterSidebar from './FilterSidebar'
|
||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import CheckIcon from '../../Icons/CheckIcon'
|
import CheckIcon from '../../Icons/CheckIcon'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate, useLocation } from 'react-router-dom'
|
||||||
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
|
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import { ElectronContext } from '../context/ElectronContext'
|
import { ElectronContext } from '../context/ElectronContext'
|
||||||
import ActionsIcon from '../../Icons/ActionsIcon'
|
import ActionsIcon from '../../Icons/ActionsIcon'
|
||||||
import FilterIcon from '../../Icons/FilterIcon'
|
import FilterIcon from '../../Icons/FilterIcon'
|
||||||
import ScrollBox from './ScrollBox'
|
import ScrollBox from './ScrollBox'
|
||||||
|
import { useTableStatePersistence } from '../context/TableStateContext'
|
||||||
|
|
||||||
const logger = loglevel.getLogger('DasboardTable')
|
const logger = loglevel.getLogger('DasboardTable')
|
||||||
logger.setLevel(config.logLevel)
|
logger.setLevel(config.logLevel)
|
||||||
@ -117,6 +118,14 @@ const ObjectTable = forwardRef(
|
|||||||
showFilterSidebar = false,
|
showFilterSidebar = false,
|
||||||
expandHeight = false,
|
expandHeight = false,
|
||||||
showActions = true,
|
showActions = true,
|
||||||
|
saveFilterInSession = false,
|
||||||
|
saveFilterInUrl = false,
|
||||||
|
useFilterInSession = false,
|
||||||
|
useFilterInUrl = false,
|
||||||
|
saveSortInSession = false,
|
||||||
|
saveSortInUrl = false,
|
||||||
|
useSortInSession = false,
|
||||||
|
useSortInUrl = false,
|
||||||
onRowAction
|
onRowAction
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
@ -139,6 +148,26 @@ const ObjectTable = forwardRef(
|
|||||||
} = useContext(ApiServerContext)
|
} = useContext(ApiServerContext)
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const {
|
||||||
|
getPersistedFilter,
|
||||||
|
getPersistedSorter,
|
||||||
|
persistFilter,
|
||||||
|
persistTableState,
|
||||||
|
registerPageFilter,
|
||||||
|
getActiveFilterValues
|
||||||
|
} = useTableStatePersistence({
|
||||||
|
scope: type,
|
||||||
|
pagePath: location.pathname,
|
||||||
|
saveFilterInSession,
|
||||||
|
saveFilterInUrl,
|
||||||
|
useFilterInSession,
|
||||||
|
useFilterInUrl,
|
||||||
|
saveSortInSession,
|
||||||
|
saveSortInUrl,
|
||||||
|
useSortInSession,
|
||||||
|
useSortInUrl
|
||||||
|
})
|
||||||
var adjustedScrollHeight = scrollHeight
|
var adjustedScrollHeight = scrollHeight
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
adjustedScrollHeight = 'calc(var(--unit-100vh) - 298px)'
|
adjustedScrollHeight = 'calc(var(--unit-100vh) - 298px)'
|
||||||
@ -160,6 +189,7 @@ const ObjectTable = forwardRef(
|
|||||||
const activeFilterRef = useRef({})
|
const activeFilterRef = useRef({})
|
||||||
const tableSorterRef = useRef({})
|
const tableSorterRef = useRef({})
|
||||||
const [sidebarFilter, setSidebarFilter] = useState({})
|
const [sidebarFilter, setSidebarFilter] = useState({})
|
||||||
|
const [tableSorter, setTableSorter] = useState({})
|
||||||
const [initialized, setInitialized] = useState(false)
|
const [initialized, setInitialized] = useState(false)
|
||||||
|
|
||||||
// Table state
|
// Table state
|
||||||
@ -781,8 +811,24 @@ const ObjectTable = forwardRef(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const loadInitialPage = useCallback(async () => {
|
const loadInitialPage = useCallback(async () => {
|
||||||
loadPage(initialPage)
|
const filter = getPersistedFilter()
|
||||||
}, [initialPage, loadPage])
|
const sorter = getPersistedSorter()
|
||||||
|
const activeFilter = getActiveFilterValues(filter)
|
||||||
|
|
||||||
|
if (Object.keys(filter).length > 0) {
|
||||||
|
setSidebarFilter(filter)
|
||||||
|
}
|
||||||
|
if (sorter.field) {
|
||||||
|
tableSorterRef.current = sorter
|
||||||
|
setTableSorter(sorter)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPage(
|
||||||
|
initialPage,
|
||||||
|
activeFilter,
|
||||||
|
sorter.field ? sorter : null
|
||||||
|
)
|
||||||
|
}, [getPersistedFilter, getPersistedSorter, initialPage, loadPage])
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
reload,
|
reload,
|
||||||
@ -823,6 +869,7 @@ const ObjectTable = forwardRef(
|
|||||||
activeFilterRef.current = {}
|
activeFilterRef.current = {}
|
||||||
tableSorterRef.current = {}
|
tableSorterRef.current = {}
|
||||||
setSidebarFilter({})
|
setSidebarFilter({})
|
||||||
|
setTableSorter({})
|
||||||
setInitialized(false)
|
setInitialized(false)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setLazyLoading(false)
|
setLazyLoading(false)
|
||||||
@ -830,6 +877,11 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
}, [type, masterFilter])
|
}, [type, masterFilter])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
registerPageFilter(sidebarFilter)
|
||||||
|
return () => registerPageFilter({})
|
||||||
|
}, [sidebarFilter, registerPageFilter])
|
||||||
|
|
||||||
const getFilterDropdown = ({
|
const getFilterDropdown = ({
|
||||||
setSelectedKeys,
|
setSelectedKeys,
|
||||||
selectedKeys,
|
selectedKeys,
|
||||||
@ -866,14 +918,6 @@ const ObjectTable = forwardRef(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getActiveFilter = useCallback((filterState) => {
|
|
||||||
const active = {}
|
|
||||||
Object.entries(filterState).forEach(([k, v]) => {
|
|
||||||
if (v !== '' && v !== undefined) active[k] = v
|
|
||||||
})
|
|
||||||
return active
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
const next = { ...sidebarFilter }
|
const next = { ...sidebarFilter }
|
||||||
|
|
||||||
@ -885,23 +929,28 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setSidebarFilter(next)
|
const nextSorter = {
|
||||||
setPages([])
|
|
||||||
setLoading(true)
|
|
||||||
loadPage(initialPage, getActiveFilter(next), {
|
|
||||||
field: sorter.columnKey,
|
field: sorter.columnKey,
|
||||||
order: sorter.order
|
order: sorter.order
|
||||||
})
|
}
|
||||||
|
|
||||||
|
setSidebarFilter(next)
|
||||||
|
setTableSorter(nextSorter)
|
||||||
|
persistTableState(next, nextSorter)
|
||||||
|
setPages([])
|
||||||
|
setLoading(true)
|
||||||
|
loadPage(initialPage, getActiveFilterValues(next), nextSorter)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSidebarFilterChange = useCallback(
|
const handleSidebarFilterChange = useCallback(
|
||||||
(newSidebarFilter) => {
|
(newSidebarFilter) => {
|
||||||
setSidebarFilter(newSidebarFilter)
|
setSidebarFilter(newSidebarFilter)
|
||||||
|
persistFilter(newSidebarFilter)
|
||||||
setPages([])
|
setPages([])
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
loadPage(initialPage, getActiveFilter(newSidebarFilter))
|
loadPage(initialPage, getActiveFilterValues(newSidebarFilter))
|
||||||
},
|
},
|
||||||
[initialPage, loadPage, getActiveFilter]
|
[initialPage, loadPage, persistFilter]
|
||||||
)
|
)
|
||||||
|
|
||||||
const modelProperties = getModelProperties(type)
|
const modelProperties = getModelProperties(type)
|
||||||
@ -988,6 +1037,8 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
const columnConfig = {
|
const columnConfig = {
|
||||||
sorter: isSortable ? { multiple: 1 } : undefined,
|
sorter: isSortable ? { multiple: 1 } : undefined,
|
||||||
|
sortOrder:
|
||||||
|
tableSorter?.field === prop.name ? tableSorter.order : null,
|
||||||
title: prop.label,
|
title: prop.label,
|
||||||
width: prop.columnWidth || width,
|
width: prop.columnWidth || width,
|
||||||
fixed: isMobile ? undefined : fixed,
|
fixed: isMobile ? undefined : fixed,
|
||||||
@ -1306,6 +1357,14 @@ ObjectTable.propTypes = {
|
|||||||
showFilterSidebar: PropTypes.bool,
|
showFilterSidebar: PropTypes.bool,
|
||||||
expandHeight: PropTypes.bool,
|
expandHeight: PropTypes.bool,
|
||||||
showActions: PropTypes.bool,
|
showActions: PropTypes.bool,
|
||||||
|
saveFilterInSession: PropTypes.bool,
|
||||||
|
saveFilterInUrl: PropTypes.bool,
|
||||||
|
useFilterInSession: PropTypes.bool,
|
||||||
|
useFilterInUrl: PropTypes.bool,
|
||||||
|
saveSortInSession: PropTypes.bool,
|
||||||
|
saveSortInUrl: PropTypes.bool,
|
||||||
|
useSortInSession: PropTypes.bool,
|
||||||
|
useSortInUrl: PropTypes.bool,
|
||||||
onRowAction: PropTypes.func
|
onRowAction: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
365
src/components/Dashboard/context/TableStateContext.jsx
Normal file
365
src/components/Dashboard/context/TableStateContext.jsx
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useState
|
||||||
|
} from 'react'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
const TableStateContext = createContext()
|
||||||
|
|
||||||
|
export const FILTER_URL_PARAM = 'filter'
|
||||||
|
export const SORT_URL_PARAM = 'sort'
|
||||||
|
|
||||||
|
const getSessionFilterKey = (scope) => `tableState:${scope}:filter`
|
||||||
|
const getSessionSortKey = (scope) => `tableState:${scope}:sort`
|
||||||
|
|
||||||
|
export const getActiveFilterValues = (filterState) => {
|
||||||
|
const active = {}
|
||||||
|
Object.entries(filterState || {}).forEach(([k, v]) => {
|
||||||
|
if (v !== '' && v !== undefined) active[k] = v
|
||||||
|
})
|
||||||
|
return active
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseJsonParam = (raw) => {
|
||||||
|
if (!raw) return null
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw)
|
||||||
|
return typeof parsed === 'object' && parsed !== null ? parsed : null
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const readFilterFromUrl = (searchParams) =>
|
||||||
|
parseJsonParam(searchParams.get(FILTER_URL_PARAM))
|
||||||
|
|
||||||
|
const readSortFromUrl = (searchParams) => {
|
||||||
|
const parsed = parseJsonParam(searchParams.get(SORT_URL_PARAM))
|
||||||
|
if (!parsed?.field || !parsed?.order) return null
|
||||||
|
return { field: parsed.field, order: parsed.order }
|
||||||
|
}
|
||||||
|
|
||||||
|
const readFilterFromSession = (scope) => {
|
||||||
|
try {
|
||||||
|
return parseJsonParam(sessionStorage.getItem(getSessionFilterKey(scope)))
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasStoredSessionFilter = (scope) => {
|
||||||
|
if (!scope) return false
|
||||||
|
return Object.keys(getActiveFilterValues(readFilterFromSession(scope) || {})).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const readSortFromSession = (scope) => {
|
||||||
|
try {
|
||||||
|
const parsed = parseJsonParam(sessionStorage.getItem(getSessionSortKey(scope)))
|
||||||
|
if (!parsed?.field || !parsed?.order) return null
|
||||||
|
return { field: parsed.field, order: parsed.order }
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeFilterToSession = (scope, filter) => {
|
||||||
|
const key = getSessionFilterKey(scope)
|
||||||
|
if (Object.keys(filter).length > 0) {
|
||||||
|
sessionStorage.setItem(key, JSON.stringify(filter))
|
||||||
|
} else {
|
||||||
|
sessionStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeSortToSession = (scope, sorter) => {
|
||||||
|
const key = getSessionSortKey(scope)
|
||||||
|
if (sorter?.field && sorter?.order) {
|
||||||
|
sessionStorage.setItem(key, JSON.stringify(sorter))
|
||||||
|
} else {
|
||||||
|
sessionStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeSorter = (sorter) => {
|
||||||
|
if (sorter?.field && sorter?.order) {
|
||||||
|
return { field: sorter.field, order: sorter.order }
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TableStateProvider = ({ children }) => {
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
const [pageFilters, setPageFilters] = useState({})
|
||||||
|
|
||||||
|
const getPersistedFilter = useCallback(
|
||||||
|
(scope, { useFilterInUrl = false, useFilterInSession = false } = {}) => {
|
||||||
|
if (useFilterInUrl) {
|
||||||
|
const fromUrl = readFilterFromUrl(searchParams)
|
||||||
|
if (fromUrl) return fromUrl
|
||||||
|
}
|
||||||
|
if (useFilterInSession) {
|
||||||
|
const fromSession = readFilterFromSession(scope)
|
||||||
|
if (fromSession) return fromSession
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
[searchParams]
|
||||||
|
)
|
||||||
|
|
||||||
|
const getPersistedSorter = useCallback(
|
||||||
|
(scope, { useSortInUrl = false, useSortInSession = false } = {}) => {
|
||||||
|
if (useSortInUrl) {
|
||||||
|
const fromUrl = readSortFromUrl(searchParams)
|
||||||
|
if (fromUrl) return fromUrl
|
||||||
|
}
|
||||||
|
if (useSortInSession) {
|
||||||
|
const fromSession = readSortFromSession(scope)
|
||||||
|
if (fromSession) return fromSession
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
[searchParams]
|
||||||
|
)
|
||||||
|
|
||||||
|
const persistFilter = useCallback(
|
||||||
|
(
|
||||||
|
scope,
|
||||||
|
filterState,
|
||||||
|
{ saveFilterInSession = false, saveFilterInUrl = false } = {}
|
||||||
|
) => {
|
||||||
|
const active = getActiveFilterValues(filterState)
|
||||||
|
if (saveFilterInSession) {
|
||||||
|
writeFilterToSession(scope, active)
|
||||||
|
}
|
||||||
|
if (saveFilterInUrl) {
|
||||||
|
const next = new URLSearchParams(searchParams)
|
||||||
|
if (Object.keys(active).length > 0) {
|
||||||
|
next.set(FILTER_URL_PARAM, JSON.stringify(active))
|
||||||
|
} else {
|
||||||
|
next.delete(FILTER_URL_PARAM)
|
||||||
|
}
|
||||||
|
setSearchParams(next, { replace: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[searchParams, setSearchParams]
|
||||||
|
)
|
||||||
|
|
||||||
|
const persistSort = useCallback(
|
||||||
|
(
|
||||||
|
scope,
|
||||||
|
sorter,
|
||||||
|
{ saveSortInSession = false, saveSortInUrl = false } = {}
|
||||||
|
) => {
|
||||||
|
const nextSorter = normalizeSorter(sorter)
|
||||||
|
if (saveSortInSession) {
|
||||||
|
writeSortToSession(scope, nextSorter)
|
||||||
|
}
|
||||||
|
if (saveSortInUrl) {
|
||||||
|
const next = new URLSearchParams(searchParams)
|
||||||
|
if (nextSorter.field && nextSorter.order) {
|
||||||
|
next.set(SORT_URL_PARAM, JSON.stringify(nextSorter))
|
||||||
|
} else {
|
||||||
|
next.delete(SORT_URL_PARAM)
|
||||||
|
}
|
||||||
|
setSearchParams(next, { replace: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[searchParams, setSearchParams]
|
||||||
|
)
|
||||||
|
|
||||||
|
const persistTableState = useCallback(
|
||||||
|
(
|
||||||
|
scope,
|
||||||
|
filterState,
|
||||||
|
sorter,
|
||||||
|
{
|
||||||
|
saveFilterInSession = false,
|
||||||
|
saveFilterInUrl = false,
|
||||||
|
saveSortInSession = false,
|
||||||
|
saveSortInUrl = false
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
|
const active = getActiveFilterValues(filterState)
|
||||||
|
const nextSorter = normalizeSorter(sorter)
|
||||||
|
|
||||||
|
if (saveFilterInSession) {
|
||||||
|
writeFilterToSession(scope, active)
|
||||||
|
}
|
||||||
|
if (saveSortInSession) {
|
||||||
|
writeSortToSession(scope, nextSorter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveFilterInUrl || saveSortInUrl) {
|
||||||
|
const next = new URLSearchParams(searchParams)
|
||||||
|
if (saveFilterInUrl) {
|
||||||
|
if (Object.keys(active).length > 0) {
|
||||||
|
next.set(FILTER_URL_PARAM, JSON.stringify(active))
|
||||||
|
} else {
|
||||||
|
next.delete(FILTER_URL_PARAM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (saveSortInUrl) {
|
||||||
|
if (nextSorter.field && nextSorter.order) {
|
||||||
|
next.set(SORT_URL_PARAM, JSON.stringify(nextSorter))
|
||||||
|
} else {
|
||||||
|
next.delete(SORT_URL_PARAM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSearchParams(next, { replace: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[searchParams, setSearchParams]
|
||||||
|
)
|
||||||
|
|
||||||
|
const setPageFilter = useCallback((path, filter) => {
|
||||||
|
const active = getActiveFilterValues(filter)
|
||||||
|
setPageFilters((prev) => {
|
||||||
|
const hasActive = Object.keys(active).length > 0
|
||||||
|
if (!hasActive) {
|
||||||
|
if (!(path in prev)) return prev
|
||||||
|
const next = { ...prev }
|
||||||
|
delete next[path]
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
return { ...prev, [path]: active }
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const hasPageFilter = useCallback(
|
||||||
|
(path) => Object.keys(pageFilters[path] || {}).length > 0,
|
||||||
|
[pageFilters]
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasStoredFilter = useCallback((scope) => hasStoredSessionFilter(scope), [])
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
getPersistedFilter,
|
||||||
|
getPersistedSorter,
|
||||||
|
persistFilter,
|
||||||
|
persistSort,
|
||||||
|
persistTableState,
|
||||||
|
setPageFilter,
|
||||||
|
hasPageFilter,
|
||||||
|
hasStoredFilter,
|
||||||
|
pageFilters
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
getPersistedFilter,
|
||||||
|
getPersistedSorter,
|
||||||
|
persistFilter,
|
||||||
|
persistSort,
|
||||||
|
persistTableState,
|
||||||
|
setPageFilter,
|
||||||
|
hasPageFilter,
|
||||||
|
hasStoredFilter,
|
||||||
|
pageFilters
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableStateContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</TableStateContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTableState = () => {
|
||||||
|
const context = useContext(TableStateContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useTableState must be used within a TableStateProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTableStatePersistence = ({
|
||||||
|
scope,
|
||||||
|
pagePath,
|
||||||
|
saveFilterInSession = false,
|
||||||
|
saveFilterInUrl = false,
|
||||||
|
useFilterInSession = false,
|
||||||
|
useFilterInUrl = false,
|
||||||
|
saveSortInSession = false,
|
||||||
|
saveSortInUrl = false,
|
||||||
|
useSortInSession = false,
|
||||||
|
useSortInUrl = false
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getPersistedFilter: getPersistedFilterCtx,
|
||||||
|
getPersistedSorter: getPersistedSorterCtx,
|
||||||
|
persistFilter: persistFilterCtx,
|
||||||
|
persistSort: persistSortCtx,
|
||||||
|
persistTableState: persistTableStateCtx,
|
||||||
|
setPageFilter
|
||||||
|
} = useTableState()
|
||||||
|
|
||||||
|
const readOptions = useMemo(
|
||||||
|
() => ({ useFilterInUrl, useFilterInSession, useSortInUrl, useSortInSession }),
|
||||||
|
[useFilterInUrl, useFilterInSession, useSortInUrl, useSortInSession]
|
||||||
|
)
|
||||||
|
|
||||||
|
const saveOptions = useMemo(
|
||||||
|
() => ({
|
||||||
|
saveFilterInSession,
|
||||||
|
saveFilterInUrl,
|
||||||
|
saveSortInSession,
|
||||||
|
saveSortInUrl
|
||||||
|
}),
|
||||||
|
[saveFilterInSession, saveFilterInUrl, saveSortInSession, saveSortInUrl]
|
||||||
|
)
|
||||||
|
|
||||||
|
const getPersistedFilter = useCallback(
|
||||||
|
() => getPersistedFilterCtx(scope, readOptions),
|
||||||
|
[getPersistedFilterCtx, scope, readOptions]
|
||||||
|
)
|
||||||
|
|
||||||
|
const getPersistedSorter = useCallback(
|
||||||
|
() => getPersistedSorterCtx(scope, readOptions),
|
||||||
|
[getPersistedSorterCtx, scope, readOptions]
|
||||||
|
)
|
||||||
|
|
||||||
|
const persistFilter = useCallback(
|
||||||
|
(filterState) => persistFilterCtx(scope, filterState, saveOptions),
|
||||||
|
[persistFilterCtx, scope, saveOptions]
|
||||||
|
)
|
||||||
|
|
||||||
|
const persistSort = useCallback(
|
||||||
|
(sorter) => persistSortCtx(scope, sorter, saveOptions),
|
||||||
|
[persistSortCtx, scope, saveOptions]
|
||||||
|
)
|
||||||
|
|
||||||
|
const persistTableState = useCallback(
|
||||||
|
(filterState, sorter) =>
|
||||||
|
persistTableStateCtx(scope, filterState, sorter, saveOptions),
|
||||||
|
[persistTableStateCtx, scope, saveOptions]
|
||||||
|
)
|
||||||
|
|
||||||
|
const registerPageFilter = useCallback(
|
||||||
|
(filterState) => {
|
||||||
|
if (pagePath) {
|
||||||
|
setPageFilter(pagePath, filterState)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[pagePath, setPageFilter]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
getPersistedFilter,
|
||||||
|
getPersistedSorter,
|
||||||
|
persistFilter,
|
||||||
|
persistSort,
|
||||||
|
persistTableState,
|
||||||
|
registerPageFilter,
|
||||||
|
getActiveFilterValues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableStateProvider.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableStateContext
|
||||||
Loading…
x
Reference in New Issue
Block a user