Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
- Integrated Badge component to display the count of active sorters in the SortSidebarButton. - Utilized useLocation and useTableState hooks to manage sorting state based on the current route. - Updated TableStateContext to include pageSorters state and setPageSorter function for improved sorting functionality across the dashboard.
399 lines
10 KiB
JavaScript
399 lines
10 KiB
JavaScript
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`
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
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
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
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 [pageSorters, setPageSorters] = 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 setPageSorter = useCallback((path, sorter) => {
|
|
const hasSort = sorter?.field && sorter?.order
|
|
setPageSorters((prev) => {
|
|
if (!hasSort) {
|
|
if (!(path in prev)) return prev
|
|
const next = { ...prev }
|
|
delete next[path]
|
|
return next
|
|
}
|
|
return { ...prev, [path]: { field: sorter.field, order: sorter.order } }
|
|
})
|
|
}, [])
|
|
|
|
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,
|
|
setPageSorter,
|
|
hasPageFilter,
|
|
hasStoredFilter,
|
|
pageFilters,
|
|
pageSorters
|
|
}),
|
|
[
|
|
getPersistedFilter,
|
|
getPersistedSorter,
|
|
persistFilter,
|
|
persistSort,
|
|
persistTableState,
|
|
setPageFilter,
|
|
setPageSorter,
|
|
hasPageFilter,
|
|
hasStoredFilter,
|
|
pageFilters,
|
|
pageSorters
|
|
]
|
|
)
|
|
|
|
return (
|
|
<TableStateContext.Provider value={value}>
|
|
{children}
|
|
</TableStateContext.Provider>
|
|
)
|
|
}
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
export const useTableState = () => {
|
|
const context = useContext(TableStateContext)
|
|
if (!context) {
|
|
throw new Error('useTableState must be used within a TableStateProvider')
|
|
}
|
|
return context
|
|
}
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
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,
|
|
setPageSorter
|
|
} = 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]
|
|
)
|
|
|
|
const registerPageSorter = useCallback(
|
|
(sorter) => {
|
|
if (pagePath) {
|
|
setPageSorter(pagePath, sorter)
|
|
}
|
|
},
|
|
[pagePath, setPageSorter]
|
|
)
|
|
|
|
return {
|
|
getPersistedFilter,
|
|
getPersistedSorter,
|
|
persistFilter,
|
|
persistSort,
|
|
persistTableState,
|
|
registerPageFilter,
|
|
registerPageSorter,
|
|
getActiveFilterValues
|
|
}
|
|
}
|
|
|
|
TableStateProvider.propTypes = {
|
|
children: PropTypes.node.isRequired
|
|
}
|
|
|
|
export default TableStateContext
|