Enhance FilterSidebar and ObjectTable Components with View Filter and User Overrides
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:
Tom Butcher 2026-09-03 16:31:07 +01:00
parent 3d1926aba0
commit 789e66b0f3
5 changed files with 218 additions and 133 deletions

View File

@ -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

View File

@ -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>
)} )}

View File

@ -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

View File

@ -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
}), }),

View File

@ -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(
() => ({ () => ({