Enhance FilterSidebar and ObjectTable Components with View Filter and User Overrides
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Added support for viewFilter and isEditing props in FilterSidebar to manage filter states more effectively. - Updated ObjectTable to handle user overrides for filter and sort states when switching views, ensuring accurate data representation. - Integrated userOverridesRef to maintain filter and sort states during editing, improving user experience when managing views. - Adjusted loading logic in ObjectTableNavigationButtons to prioritize effective filters based on active views, enhancing data consistency.
This commit is contained in:
parent
3d1926aba0
commit
789e66b0f3
@ -45,7 +45,9 @@ const FilterSidebar = ({
|
|||||||
type,
|
type,
|
||||||
filter = {},
|
filter = {},
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
masterFilter = {}
|
masterFilter = {},
|
||||||
|
viewFilter = {},
|
||||||
|
isEditing = false
|
||||||
}) => {
|
}) => {
|
||||||
const model = getModelByName(type)
|
const model = getModelByName(type)
|
||||||
const modelProps = getModelProperties(type)
|
const modelProps = getModelProperties(type)
|
||||||
@ -113,6 +115,11 @@ const FilterSidebar = ({
|
|||||||
[filterableProperties]
|
[filterableProperties]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const viewFilterKeys = useMemo(
|
||||||
|
() => new Set(Object.keys(viewFilter).filter((k) => viewFilter[k] !== '')),
|
||||||
|
[viewFilter]
|
||||||
|
)
|
||||||
|
|
||||||
const activeFields = Object.keys(localFilter)
|
const activeFields = Object.keys(localFilter)
|
||||||
|
|
||||||
const usedFields = new Set(activeFields)
|
const usedFields = new Set(activeFields)
|
||||||
@ -192,39 +199,46 @@ const FilterSidebar = ({
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex vertical gap={8}>
|
<Flex vertical gap={8}>
|
||||||
{rows.map((row) => (
|
{rows.map((row) => {
|
||||||
<Space.Compact key={row.field} style={{ width: '100%' }}>
|
const isViewRow =
|
||||||
<Select
|
viewFilterKeys.has(row.field) && !isEditing
|
||||||
placeholder='Field'
|
return (
|
||||||
value={row.field || undefined}
|
<Space.Compact key={row.field} style={{ width: '100%' }}>
|
||||||
onChange={(v) => changeField(row.field, v)}
|
<Select
|
||||||
options={availableOptions(row.field)}
|
placeholder='Field'
|
||||||
allowClear={false}
|
value={row.field || undefined}
|
||||||
labelRender={({ label }) => (
|
onChange={(v) => changeField(row.field, v)}
|
||||||
<FieldSelectLabel label={label} options={fieldOptions} />
|
options={availableOptions(row.field)}
|
||||||
)}
|
allowClear={false}
|
||||||
style={{ flexShrink: 0 }}
|
disabled={isViewRow}
|
||||||
classNames={{ root: 'filter-sidebar-field-select' }}
|
labelRender={({ label }) => (
|
||||||
/>
|
<FieldSelectLabel label={label} options={fieldOptions} />
|
||||||
<FilterInput
|
)}
|
||||||
placeholder='Value'
|
style={{ flexShrink: 0 }}
|
||||||
value={row.value}
|
classNames={{ root: 'filter-sidebar-field-select' }}
|
||||||
onChange={(value) => changeValue(row.field, value)}
|
/>
|
||||||
style={{ flex: 1 }}
|
<FilterInput
|
||||||
propertyFilter={{
|
placeholder='Value'
|
||||||
modelType: type,
|
value={row.value}
|
||||||
propertyName: row.field
|
onChange={(value) => changeValue(row.field, value)}
|
||||||
}}
|
style={{ flex: 1 }}
|
||||||
filter={localFilter}
|
disabled={isViewRow}
|
||||||
masterFilter={masterFilter}
|
propertyFilter={{
|
||||||
/>
|
modelType: type,
|
||||||
<Button
|
propertyName: row.field
|
||||||
icon={<CloseOutlined />}
|
}}
|
||||||
onClick={() => removeRow(row.field)}
|
filter={localFilter}
|
||||||
title='Remove filter'
|
masterFilter={masterFilter}
|
||||||
/>
|
/>
|
||||||
</Space.Compact>
|
<Button
|
||||||
))}
|
icon={<CloseOutlined />}
|
||||||
|
onClick={() => removeRow(row.field)}
|
||||||
|
title='Remove filter'
|
||||||
|
disabled={isViewRow}
|
||||||
|
/>
|
||||||
|
</Space.Compact>
|
||||||
|
)
|
||||||
|
})}
|
||||||
{rows.length === 0 && (
|
{rows.length === 0 && (
|
||||||
<MissingPlaceholder message='No filters applied.' />
|
<MissingPlaceholder message='No filters applied.' />
|
||||||
)}
|
)}
|
||||||
@ -238,7 +252,9 @@ FilterSidebar.propTypes = {
|
|||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
filter: PropTypes.object,
|
filter: PropTypes.object,
|
||||||
onFilterChange: PropTypes.func.isRequired,
|
onFilterChange: PropTypes.func.isRequired,
|
||||||
masterFilter: PropTypes.object
|
masterFilter: PropTypes.object,
|
||||||
|
viewFilter: PropTypes.object,
|
||||||
|
isEditing: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilterSidebar
|
export default FilterSidebar
|
||||||
|
|||||||
@ -329,6 +329,7 @@ const ObjectTable = forwardRef(
|
|||||||
const listViewFilter = objectListView?.listViewFilter ?? null
|
const listViewFilter = objectListView?.listViewFilter ?? null
|
||||||
const listViewSort = objectListView?.listViewSort ?? null
|
const listViewSort = objectListView?.listViewSort ?? null
|
||||||
const activeObjectView = objectListView?.activeObjectView ?? null
|
const activeObjectView = objectListView?.activeObjectView ?? null
|
||||||
|
const userOverridesRef = objectListView?.userOverridesRef ?? null
|
||||||
const onObjectViewFilterSortChange = objectListView?.isEditing
|
const onObjectViewFilterSortChange = objectListView?.isEditing
|
||||||
? objectListView.handleFilterSortChange
|
? objectListView.handleFilterSortChange
|
||||||
: null
|
: null
|
||||||
@ -1369,12 +1370,6 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
if (lastAppliedViewKeyRef.current === viewKey) return
|
if (lastAppliedViewKeyRef.current === viewKey) return
|
||||||
|
|
||||||
// Prop lag: listViewId is set but filter/sort props are still empty.
|
|
||||||
// Never overwrite a non-empty sidebar/query with {}.
|
|
||||||
const hasViewState =
|
|
||||||
Object.keys(viewFilter).length > 0 || Boolean(viewSort.field)
|
|
||||||
if (!hasViewState) return
|
|
||||||
|
|
||||||
lastAppliedViewKeyRef.current = viewKey
|
lastAppliedViewKeyRef.current = viewKey
|
||||||
|
|
||||||
assignSidebarToView(listViewId)
|
assignSidebarToView(listViewId)
|
||||||
@ -1394,6 +1389,9 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
const applyViewState = useCallback(
|
const applyViewState = useCallback(
|
||||||
(filterState, sorterState, options = {}) => {
|
(filterState, sorterState, options = {}) => {
|
||||||
|
// Switching views clears user overrides
|
||||||
|
if (userOverridesRef) userOverridesRef.current = null
|
||||||
|
|
||||||
let filter
|
let filter
|
||||||
let sorter
|
let sorter
|
||||||
|
|
||||||
@ -1630,7 +1628,16 @@ const ObjectTable = forwardRef(
|
|||||||
listViewFilterRef.current = next
|
listViewFilterRef.current = next
|
||||||
// Editing a view: keep filter/sort on the draft view only
|
// Editing a view: keep filter/sort on the draft view only
|
||||||
onObjectViewFilterSortChangeRef.current?.(next, nextSorter)
|
onObjectViewFilterSortChangeRef.current?.(next, nextSorter)
|
||||||
} else if (!listViewIdRef.current) {
|
} else if (listViewIdRef.current) {
|
||||||
|
// On a view but not editing: track as user override for startEditing
|
||||||
|
if (userOverridesRef) {
|
||||||
|
userOverridesRef.current = {
|
||||||
|
...(userOverridesRef.current || {}),
|
||||||
|
filter: next,
|
||||||
|
sort: nextSorter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// All tab: persist personal filter/sort to URL/session
|
// All tab: persist personal filter/sort to URL/session
|
||||||
persistTableState(next, nextSorter)
|
persistTableState(next, nextSorter)
|
||||||
}
|
}
|
||||||
@ -1651,7 +1658,20 @@ const ObjectTable = forwardRef(
|
|||||||
newSidebarFilter,
|
newSidebarFilter,
|
||||||
tableSorter
|
tableSorter
|
||||||
)
|
)
|
||||||
} else if (!listViewIdRef.current) {
|
} else if (listViewIdRef.current) {
|
||||||
|
// On a view but not editing: track as user override for startEditing
|
||||||
|
if (userOverridesRef) {
|
||||||
|
userOverridesRef.current = {
|
||||||
|
...(userOverridesRef.current || {}),
|
||||||
|
filter: newSidebarFilter,
|
||||||
|
sort:
|
||||||
|
userOverridesRef.current?.sort ??
|
||||||
|
(tableSorterRef.current?.field
|
||||||
|
? tableSorterRef.current
|
||||||
|
: undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
persistFilter(newSidebarFilter)
|
persistFilter(newSidebarFilter)
|
||||||
}
|
}
|
||||||
const effective = buildEffectiveFilter(newSidebarFilter)
|
const effective = buildEffectiveFilter(newSidebarFilter)
|
||||||
@ -1667,7 +1687,8 @@ const ObjectTable = forwardRef(
|
|||||||
loadPage,
|
loadPage,
|
||||||
persistFilter,
|
persistFilter,
|
||||||
resolveSorter,
|
resolveSorter,
|
||||||
tableSorter
|
tableSorter,
|
||||||
|
userOverridesRef
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1681,7 +1702,16 @@ const ObjectTable = forwardRef(
|
|||||||
tableSorterRef.current = nextSorter
|
tableSorterRef.current = nextSorter
|
||||||
if (activeObjectViewRef.current) {
|
if (activeObjectViewRef.current) {
|
||||||
onObjectViewFilterSortChangeRef.current?.(sidebarFilter, nextSorter)
|
onObjectViewFilterSortChangeRef.current?.(sidebarFilter, nextSorter)
|
||||||
} else if (!listViewIdRef.current) {
|
} else if (listViewIdRef.current) {
|
||||||
|
// On a view but not editing: track as user override for startEditing
|
||||||
|
if (userOverridesRef) {
|
||||||
|
userOverridesRef.current = {
|
||||||
|
...(userOverridesRef.current || {}),
|
||||||
|
sort: nextSorter,
|
||||||
|
filter: userOverridesRef.current?.filter ?? sidebarFilter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
persistSort(nextSorter)
|
persistSort(nextSorter)
|
||||||
}
|
}
|
||||||
const effective = buildEffectiveFilter(sidebarFilter)
|
const effective = buildEffectiveFilter(sidebarFilter)
|
||||||
@ -1696,7 +1726,8 @@ const ObjectTable = forwardRef(
|
|||||||
persistSort,
|
persistSort,
|
||||||
resolveSorter,
|
resolveSorter,
|
||||||
sidebarFilter,
|
sidebarFilter,
|
||||||
suppressTableChange
|
suppressTableChange,
|
||||||
|
userOverridesRef
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2157,6 +2188,8 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
onFilterChange={handleSidebarFilterChange}
|
onFilterChange={handleSidebarFilterChange}
|
||||||
masterFilter={resolvedMasterFilter}
|
masterFilter={resolvedMasterFilter}
|
||||||
|
viewFilter={listViewFilter}
|
||||||
|
isEditing={!!activeObjectView}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -26,7 +26,8 @@ const ObjectTableNavigationButtons = ({
|
|||||||
const [neighbors, setNeighbors] = useState({ next: null, previous: null })
|
const [neighbors, setNeighbors] = useState({ next: null, previous: null })
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
const { getObjectListFilter, getObjectListSorter } = useTableState()
|
const { getObjectListFilter, getObjectListSorter, getObjectListView } =
|
||||||
|
useTableState()
|
||||||
const { getPersistedFilter, getPersistedSorter } = useTableStatePersistence({
|
const { getPersistedFilter, getPersistedSorter } = useTableStatePersistence({
|
||||||
scope: objectType,
|
scope: objectType,
|
||||||
useFilterInSession: true,
|
useFilterInSession: true,
|
||||||
@ -42,6 +43,9 @@ const ObjectTableNavigationButtons = ({
|
|||||||
const getObjectListSorterRef = useRef(getObjectListSorter)
|
const getObjectListSorterRef = useRef(getObjectListSorter)
|
||||||
getObjectListSorterRef.current = getObjectListSorter
|
getObjectListSorterRef.current = getObjectListSorter
|
||||||
|
|
||||||
|
const getObjectListViewRef = useRef(getObjectListView)
|
||||||
|
getObjectListViewRef.current = getObjectListView
|
||||||
|
|
||||||
const getPersistedFilterRef = useRef(getPersistedFilter)
|
const getPersistedFilterRef = useRef(getPersistedFilter)
|
||||||
getPersistedFilterRef.current = getPersistedFilter
|
getPersistedFilterRef.current = getPersistedFilter
|
||||||
|
|
||||||
@ -59,18 +63,23 @@ const ObjectTableNavigationButtons = ({
|
|||||||
|
|
||||||
const loadNeighbors = async () => {
|
const loadNeighbors = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
// Prefer the effective list filter (view + user filters) when available
|
// When a view is active, use its effective filter exclusively — never
|
||||||
|
// fall back to the All-tab persisted filter which belongs to a different
|
||||||
|
// context and would produce wrong neighbors.
|
||||||
|
const hasActiveView = !!getObjectListViewRef.current(objectType)
|
||||||
const listFilter = getActiveFilterValues(
|
const listFilter = getActiveFilterValues(
|
||||||
getObjectListFilterRef.current(objectType)
|
getObjectListFilterRef.current(objectType)
|
||||||
)
|
)
|
||||||
const persistedFilter = getActiveFilterValues(
|
const persistedFilter = hasActiveView
|
||||||
getPersistedFilterRef.current()
|
? {}
|
||||||
)
|
: getActiveFilterValues(getPersistedFilterRef.current())
|
||||||
const filter =
|
const filter =
|
||||||
Object.keys(listFilter).length > 0 ? listFilter : persistedFilter
|
Object.keys(listFilter).length > 0 ? listFilter : persistedFilter
|
||||||
|
|
||||||
const listSorter = getObjectListSorterRef.current(objectType)
|
const listSorter = getObjectListSorterRef.current(objectType)
|
||||||
const persistedSorter = getPersistedSorterRef.current()
|
const persistedSorter = hasActiveView
|
||||||
|
? {}
|
||||||
|
: getPersistedSorterRef.current()
|
||||||
const sorter =
|
const sorter =
|
||||||
listSorter?.field && listSorter?.order ? listSorter : persistedSorter
|
listSorter?.field && listSorter?.order ? listSorter : persistedSorter
|
||||||
|
|
||||||
|
|||||||
@ -13,10 +13,7 @@ import { ApiServerContext } from './ApiServerContext'
|
|||||||
import { AuthContext } from './AuthContext'
|
import { AuthContext } from './AuthContext'
|
||||||
import { useTableState } from './TableStateContext'
|
import { useTableState } from './TableStateContext'
|
||||||
import useViewMode from '../hooks/useViewMode'
|
import useViewMode from '../hooks/useViewMode'
|
||||||
import {
|
import { DEFAULT_VIEW_MODE, normalizeViewMode } from '../common/viewModeUtils'
|
||||||
DEFAULT_VIEW_MODE,
|
|
||||||
normalizeViewMode
|
|
||||||
} from '../common/viewModeUtils'
|
|
||||||
|
|
||||||
const ObjectListViewContext = createContext()
|
const ObjectListViewContext = createContext()
|
||||||
|
|
||||||
@ -25,8 +22,7 @@ const ALL_TAB_KEY = 'all'
|
|||||||
const getLastViewStorageKey = (objectType) =>
|
const getLastViewStorageKey = (objectType) =>
|
||||||
`objectListView:${objectType}:lastViewId`
|
`objectListView:${objectType}:lastViewId`
|
||||||
|
|
||||||
const getViewsCacheKey = (objectType) =>
|
const getViewsCacheKey = (objectType) => `objectListView:${objectType}:views`
|
||||||
`objectListView:${objectType}:views`
|
|
||||||
|
|
||||||
const readLastViewId = (objectType) => {
|
const readLastViewId = (objectType) => {
|
||||||
if (!objectType) return null
|
if (!objectType) return null
|
||||||
@ -99,9 +95,7 @@ export const getObjectViewKey = (view) =>
|
|||||||
|
|
||||||
const resolveViewFromId = (views, viewId) => {
|
const resolveViewFromId = (views, viewId) => {
|
||||||
if (!viewId) return null
|
if (!viewId) return null
|
||||||
return (
|
return views.find((view) => String(view._id) === String(viewId)) || null
|
||||||
views.find((view) => String(view._id) === String(viewId)) || null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cloneViews = (views) =>
|
const cloneViews = (views) =>
|
||||||
@ -121,11 +115,7 @@ const viewStateEqual = (left, right) =>
|
|||||||
const viewModeEqual = (left, right) =>
|
const viewModeEqual = (left, right) =>
|
||||||
JSON.stringify(left ?? null) === JSON.stringify(right ?? null)
|
JSON.stringify(left ?? null) === JSON.stringify(right ?? null)
|
||||||
|
|
||||||
export const ObjectListViewProvider = ({
|
export const ObjectListViewProvider = ({ children, objectType, tableRef }) => {
|
||||||
children,
|
|
||||||
objectType,
|
|
||||||
tableRef
|
|
||||||
}) => {
|
|
||||||
const {
|
const {
|
||||||
fetchObjectViews,
|
fetchObjectViews,
|
||||||
createObjectView,
|
createObjectView,
|
||||||
@ -138,9 +128,7 @@ export const ObjectListViewProvider = ({
|
|||||||
const { token, authInitialized } = useContext(AuthContext)
|
const { token, authInitialized } = useContext(AuthContext)
|
||||||
const { getViewFromUrl, persistView, setObjectListView } = useTableState()
|
const { getViewFromUrl, persistView, setObjectListView } = useTableState()
|
||||||
|
|
||||||
const [views, setViews] = useState(
|
const [views, setViews] = useState(() => readCachedViews(objectType) || [])
|
||||||
() => readCachedViews(objectType) || []
|
|
||||||
)
|
|
||||||
const [draftViews, setDraftViews] = useState(() => {
|
const [draftViews, setDraftViews] = useState(() => {
|
||||||
const cached = readCachedViews(objectType)
|
const cached = readCachedViews(objectType)
|
||||||
return cached ? cloneViews(cached) : []
|
return cached ? cloneViews(cached) : []
|
||||||
@ -155,10 +143,14 @@ export const ObjectListViewProvider = ({
|
|||||||
activeTabKeyRef.current = activeTabKey
|
activeTabKeyRef.current = activeTabKey
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const [viewSyncReady, setViewSyncReady] = useState(false)
|
const [viewSyncReady, setViewSyncReady] = useState(false)
|
||||||
|
const [userViewModeOverride, setUserViewModeOverride] = useState(null)
|
||||||
|
|
||||||
const editSnapshotRef = useRef([])
|
const editSnapshotRef = useRef([])
|
||||||
const wasEditingRef = useRef(false)
|
const wasEditingRef = useRef(false)
|
||||||
const isEditingRef = useRef(isEditing)
|
const isEditingRef = useRef(isEditing)
|
||||||
|
// ObjectTable writes its current user overrides (filter/sort) here so
|
||||||
|
// startEditing can merge them into the draft view without prop drilling.
|
||||||
|
const userOverridesRef = useRef(null)
|
||||||
const activeViewRef = useRef(null)
|
const activeViewRef = useRef(null)
|
||||||
const viewsStateRef = useRef({ views, draftViews, isEditing })
|
const viewsStateRef = useRef({ views, draftViews, isEditing })
|
||||||
const urlSyncedRef = useRef(false)
|
const urlSyncedRef = useRef(false)
|
||||||
@ -188,8 +180,9 @@ export const ObjectListViewProvider = ({
|
|||||||
const activeView = useMemo(() => {
|
const activeView = useMemo(() => {
|
||||||
if (activeTabKey === ALL_TAB_KEY) return null
|
if (activeTabKey === ALL_TAB_KEY) return null
|
||||||
const view =
|
const view =
|
||||||
displayedViews.find((item) => String(item._id) === String(activeTabKey)) ||
|
displayedViews.find(
|
||||||
null
|
(item) => String(item._id) === String(activeTabKey)
|
||||||
|
) || null
|
||||||
if (view?._isDraft) {
|
if (view?._isDraft) {
|
||||||
return { ...view, filter: view.filter || {}, sort: view.sort || {} }
|
return { ...view, filter: view.filter || {}, sort: view.sort || {} }
|
||||||
}
|
}
|
||||||
@ -213,8 +206,11 @@ export const ObjectListViewProvider = ({
|
|||||||
(tabKey) => {
|
(tabKey) => {
|
||||||
if (!tableRef?.current?.applyViewState) return
|
if (!tableRef?.current?.applyViewState) return
|
||||||
|
|
||||||
const { views: savedViews, draftViews: drafts, isEditing: editing } =
|
const {
|
||||||
viewsStateRef.current
|
views: savedViews,
|
||||||
|
draftViews: drafts,
|
||||||
|
isEditing: editing
|
||||||
|
} = viewsStateRef.current
|
||||||
const list = editing ? drafts : savedViews
|
const list = editing ? drafts : savedViews
|
||||||
const view =
|
const view =
|
||||||
tabKey === ALL_TAB_KEY
|
tabKey === ALL_TAB_KEY
|
||||||
@ -289,10 +285,7 @@ export const ObjectListViewProvider = ({
|
|||||||
|
|
||||||
persistView(key)
|
persistView(key)
|
||||||
writeLastViewId(objectType, key)
|
writeLastViewId(objectType, key)
|
||||||
setObjectListView(
|
setObjectListView(objectType, key ? { id: key, name } : null)
|
||||||
objectType,
|
|
||||||
key ? { id: key, name } : null
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
[objectType, persistView, setObjectListView]
|
[objectType, persistView, setObjectListView]
|
||||||
)
|
)
|
||||||
@ -402,6 +395,8 @@ export const ObjectListViewProvider = ({
|
|||||||
(tabKey) => {
|
(tabKey) => {
|
||||||
if (String(tabKey) === String(activeTabKey)) return
|
if (String(tabKey) === String(activeTabKey)) return
|
||||||
|
|
||||||
|
setUserViewModeOverride(null)
|
||||||
|
userOverridesRef.current = null
|
||||||
setActiveTabKey(tabKey)
|
setActiveTabKey(tabKey)
|
||||||
|
|
||||||
if (tabKey === ALL_TAB_KEY) {
|
if (tabKey === ALL_TAB_KEY) {
|
||||||
@ -431,7 +426,24 @@ export const ObjectListViewProvider = ({
|
|||||||
const startEditing = useCallback(() => {
|
const startEditing = useCallback(() => {
|
||||||
const snapshot = cloneViews(views)
|
const snapshot = cloneViews(views)
|
||||||
editSnapshotRef.current = snapshot
|
editSnapshotRef.current = snapshot
|
||||||
|
|
||||||
|
// If the user had filter/sort/viewMode overrides while browsing a view,
|
||||||
|
// merge them into the active draft so the save picks them up.
|
||||||
|
const overrides = userOverridesRef.current
|
||||||
|
if (overrides && activeTabKeyRef.current !== ALL_TAB_KEY) {
|
||||||
|
const activeId = activeTabKeyRef.current
|
||||||
|
for (const draft of snapshot) {
|
||||||
|
if (String(draft._id) !== String(activeId)) continue
|
||||||
|
if (overrides.filter) draft.filter = overrides.filter
|
||||||
|
if (overrides.sort) draft.sort = overrides.sort
|
||||||
|
if (overrides.viewMode !== undefined)
|
||||||
|
draft.viewMode = overrides.viewMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setDraftViews(snapshot)
|
setDraftViews(snapshot)
|
||||||
|
setUserViewModeOverride(null)
|
||||||
|
userOverridesRef.current = null
|
||||||
setIsEditing(true)
|
setIsEditing(true)
|
||||||
}, [views])
|
}, [views])
|
||||||
|
|
||||||
@ -440,6 +452,7 @@ export const ObjectListViewProvider = ({
|
|||||||
setDraftViews(snapshot)
|
setDraftViews(snapshot)
|
||||||
setViews(snapshot)
|
setViews(snapshot)
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
|
userOverridesRef.current = null
|
||||||
|
|
||||||
if (activeTabKey !== ALL_TAB_KEY) {
|
if (activeTabKey !== ALL_TAB_KEY) {
|
||||||
const stillExists = snapshot.some(
|
const stillExists = snapshot.some(
|
||||||
@ -458,8 +471,12 @@ export const ObjectListViewProvider = ({
|
|||||||
const saveEdits = useCallback(async () => {
|
const saveEdits = useCallback(async () => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
const originalById = new Map(views.map((view) => [String(view._id), view]))
|
const originalById = new Map(
|
||||||
const draftById = new Map(draftViews.map((view) => [String(view._id), view]))
|
views.map((view) => [String(view._id), view])
|
||||||
|
)
|
||||||
|
const draftById = new Map(
|
||||||
|
draftViews.map((view) => [String(view._id), view])
|
||||||
|
)
|
||||||
|
|
||||||
let newActiveId = activeTabKey
|
let newActiveId = activeTabKey
|
||||||
|
|
||||||
@ -542,13 +559,7 @@ export const ObjectListViewProvider = ({
|
|||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
}, [
|
}, [activeTabKey, draftViews, objectType, rememberActiveView, views])
|
||||||
activeTabKey,
|
|
||||||
draftViews,
|
|
||||||
objectType,
|
|
||||||
rememberActiveView,
|
|
||||||
views
|
|
||||||
])
|
|
||||||
|
|
||||||
const handleTabEdit = useCallback(
|
const handleTabEdit = useCallback(
|
||||||
async (targetKeyOrEvent, action) => {
|
async (targetKeyOrEvent, action) => {
|
||||||
@ -642,40 +653,45 @@ export const ObjectListViewProvider = ({
|
|||||||
)
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const reorderDraftViews = useCallback((fromValue, toValue, insertBefore) => {
|
const reorderDraftViews = useCallback(
|
||||||
if (!isEditing) return
|
(fromValue, toValue, insertBefore) => {
|
||||||
|
if (!isEditing) return
|
||||||
|
|
||||||
setDraftViews((prev) => {
|
setDraftViews((prev) => {
|
||||||
const fromIndex = prev.findIndex(
|
const fromIndex = prev.findIndex(
|
||||||
(view) => String(view._id) === String(fromValue)
|
(view) => String(view._id) === String(fromValue)
|
||||||
)
|
)
|
||||||
const toIndex = prev.findIndex(
|
const toIndex = prev.findIndex(
|
||||||
(view) => String(view._id) === String(toValue)
|
(view) => String(view._id) === String(toValue)
|
||||||
)
|
)
|
||||||
if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {
|
if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetIndex = insertBefore ? toIndex : toIndex + 1
|
let targetIndex = insertBefore ? toIndex : toIndex + 1
|
||||||
if (fromIndex < targetIndex) {
|
if (fromIndex < targetIndex) {
|
||||||
targetIndex -= 1
|
targetIndex -= 1
|
||||||
}
|
}
|
||||||
if (fromIndex === targetIndex) {
|
if (fromIndex === targetIndex) {
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = [...prev]
|
const next = [...prev]
|
||||||
const [moved] = next.splice(fromIndex, 1)
|
const [moved] = next.splice(fromIndex, 1)
|
||||||
next.splice(targetIndex, 0, moved)
|
next.splice(targetIndex, 0, moved)
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
}, [isEditing])
|
},
|
||||||
|
[isEditing]
|
||||||
|
)
|
||||||
|
|
||||||
const mergeViewUpdate = useCallback(
|
const mergeViewUpdate = useCallback(
|
||||||
(viewId, updatedData) => {
|
(viewId, updatedData) => {
|
||||||
const mergeOne = (prev) =>
|
const mergeOne = (prev) =>
|
||||||
prev.map((view) =>
|
prev.map((view) =>
|
||||||
String(view._id) === String(viewId) ? { ...view, ...updatedData } : view
|
String(view._id) === String(viewId)
|
||||||
|
? { ...view, ...updatedData }
|
||||||
|
: view
|
||||||
)
|
)
|
||||||
|
|
||||||
setViews(mergeOne)
|
setViews(mergeOne)
|
||||||
@ -694,7 +710,9 @@ export const ObjectListViewProvider = ({
|
|||||||
? getPreviousTabKey(list, viewId)
|
? getPreviousTabKey(list, viewId)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
setViews((prev) => prev.filter((view) => String(view._id) !== String(viewId)))
|
setViews((prev) =>
|
||||||
|
prev.filter((view) => String(view._id) !== String(viewId))
|
||||||
|
)
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
setDraftViews((prev) =>
|
setDraftViews((prev) =>
|
||||||
prev.filter((view) => String(view._id) !== String(viewId))
|
prev.filter((view) => String(view._id) !== String(viewId))
|
||||||
@ -716,7 +734,14 @@ export const ObjectListViewProvider = ({
|
|||||||
applyViewToTable(previousKey)
|
applyViewToTable(previousKey)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeTabKey, applyViewToTable, draftViews, isEditing, rememberActiveView, views]
|
[
|
||||||
|
activeTabKey,
|
||||||
|
applyViewToTable,
|
||||||
|
draftViews,
|
||||||
|
isEditing,
|
||||||
|
rememberActiveView,
|
||||||
|
views
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleFilterSortChange = useCallback((filter, sorter) => {
|
const handleFilterSortChange = useCallback((filter, sorter) => {
|
||||||
@ -767,10 +792,7 @@ export const ObjectListViewProvider = ({
|
|||||||
|
|
||||||
updateEventHandlerRef.current = updateEventHandler
|
updateEventHandlerRef.current = updateEventHandler
|
||||||
|
|
||||||
const subscriptionFilter = useMemo(
|
const subscriptionFilter = useMemo(() => ({ objectType }), [objectType])
|
||||||
() => ({ objectType }),
|
|
||||||
[objectType]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (connected !== true || !objectType || !authReady) return
|
if (connected !== true || !objectType || !authReady) return
|
||||||
@ -860,9 +882,7 @@ export const ObjectListViewProvider = ({
|
|||||||
? String(activeTabKey)
|
? String(activeTabKey)
|
||||||
: null
|
: null
|
||||||
const listViewFilter = activeView?.filter || EMPTY_FILTER
|
const listViewFilter = activeView?.filter || EMPTY_FILTER
|
||||||
const listViewSort = activeView?.sort?.field
|
const listViewSort = activeView?.sort?.field ? activeView.sort : EMPTY_SORT
|
||||||
? activeView.sort
|
|
||||||
: EMPTY_SORT
|
|
||||||
const listViewViewMode = activeView?.viewMode ?? null
|
const listViewViewMode = activeView?.viewMode ?? null
|
||||||
// Only expose the editable view while editing — ObjectTable uses this to
|
// Only expose the editable view while editing — ObjectTable uses this to
|
||||||
// route filter/sort changes into the draft instead of URL/session persistence.
|
// route filter/sort changes into the draft instead of URL/session persistence.
|
||||||
@ -876,9 +896,7 @@ export const ObjectListViewProvider = ({
|
|||||||
if (isEditingRef.current) {
|
if (isEditingRef.current) {
|
||||||
const updateInList = (list) =>
|
const updateInList = (list) =>
|
||||||
list.map((item) =>
|
list.map((item) =>
|
||||||
String(item._id) === String(view._id)
|
String(item._id) === String(view._id) ? { ...item, viewMode } : item
|
||||||
? { ...item, viewMode }
|
|
||||||
: item
|
|
||||||
)
|
)
|
||||||
|
|
||||||
activeViewRef.current = { ...view, viewMode }
|
activeViewRef.current = { ...view, viewMode }
|
||||||
@ -891,20 +909,22 @@ export const ObjectListViewProvider = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Not editing: store as a temporary user override instead of saving
|
||||||
await apiRef.current.updateObjectView(view._id, { viewMode })
|
setUserViewModeOverride(viewMode)
|
||||||
mergeViewUpdate(view._id, { viewMode })
|
userOverridesRef.current = {
|
||||||
} catch (error) {
|
...(userOverridesRef.current || {}),
|
||||||
console.error('Error updating view mode:', error)
|
viewMode
|
||||||
message.error('Failed to save view mode')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mergeViewUpdate]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
const isCustomView = activeTabKey !== ALL_TAB_KEY
|
const isCustomView = activeTabKey !== ALL_TAB_KEY
|
||||||
|
|
||||||
const viewMode = useMemo(() => {
|
const viewMode = useMemo(() => {
|
||||||
|
if (isCustomView && userViewModeOverride) {
|
||||||
|
return normalizeViewMode(userViewModeOverride)
|
||||||
|
}
|
||||||
if (isCustomView && listViewViewMode) {
|
if (isCustomView && listViewViewMode) {
|
||||||
return normalizeViewMode(listViewViewMode)
|
return normalizeViewMode(listViewViewMode)
|
||||||
}
|
}
|
||||||
@ -912,7 +932,7 @@ export const ObjectListViewProvider = ({
|
|||||||
return DEFAULT_VIEW_MODE
|
return DEFAULT_VIEW_MODE
|
||||||
}
|
}
|
||||||
return normalizeViewMode(userViewMode)
|
return normalizeViewMode(userViewMode)
|
||||||
}, [isCustomView, listViewViewMode, userViewMode])
|
}, [isCustomView, listViewViewMode, userViewMode, userViewModeOverride])
|
||||||
|
|
||||||
const setViewMode = useCallback(
|
const setViewMode = useCallback(
|
||||||
(nextMode) => {
|
(nextMode) => {
|
||||||
@ -963,6 +983,7 @@ export const ObjectListViewProvider = ({
|
|||||||
removeView,
|
removeView,
|
||||||
handleFilterSortChange,
|
handleFilterSortChange,
|
||||||
handleViewModeChange,
|
handleViewModeChange,
|
||||||
|
userOverridesRef,
|
||||||
reloadViews: () => loadViewsRef.current(true),
|
reloadViews: () => loadViewsRef.current(true),
|
||||||
viewTabSignatures
|
viewTabSignatures
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -481,10 +481,16 @@ export const TableStateProvider = ({ children }) => {
|
|||||||
|
|
||||||
const hasStoredFilter = useCallback((scope) => {
|
const hasStoredFilter = useCallback((scope) => {
|
||||||
if (!scope) return false
|
if (!scope) return false
|
||||||
|
// When a view is active, only report the effective list filter (which
|
||||||
|
// already accounts for the view). Don't fall back to the All-tab session
|
||||||
|
// filter — that belongs to a different context.
|
||||||
|
if (objectListViews[scope]) {
|
||||||
|
return Object.keys(objectListFilters[scope] || {}).length > 0
|
||||||
|
}
|
||||||
if (hasStoredSessionFilter(scope)) return true
|
if (hasStoredSessionFilter(scope)) return true
|
||||||
if (Object.keys(objectListFilters[scope] || {}).length > 0) return true
|
if (Object.keys(objectListFilters[scope] || {}).length > 0) return true
|
||||||
return Object.keys(getActiveFilterValues(readListFilterFromSession(scope) || {})).length > 0
|
return Object.keys(getActiveFilterValues(readListFilterFromSession(scope) || {})).length > 0
|
||||||
}, [objectListFilters])
|
}, [objectListFilters, objectListViews])
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user