diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx
index ae39fa72..c9e5f393 100644
--- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx
+++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx
@@ -28,7 +28,7 @@ const mainSections = ['production', 'inventory', 'management', 'developer']
const DashboardBreadcrumb = () => {
const location = useLocation()
const navigate = useNavigate()
- const { hasPageFilter, hasStoredFilter } = useTableState()
+ const { hasPageFilter, hasStoredFilter, getObjectListView } = useTableState()
const pathSnippets = location.pathname.split('/').filter((i) => i)
function segmentToModel(segment) {
@@ -45,7 +45,15 @@ const DashboardBreadcrumb = () => {
const isMainSection = mainSections.includes(segment)
const model = segmentToModel(segment)
const modelLabelPlural = model?.labelPlural ? model.labelPlural : null
+ const viewName =
+ modelLabelPlural && model?.name && model.name !== 'unknown'
+ ? getObjectListView(model.name)?.name
+ : null
const name = breadcrumbNameMap[segment] || modelLabelPlural || segment
+ const displayName =
+ modelLabelPlural && viewName
+ ? `${modelLabelPlural} - ${viewName}`
+ : name
const showFilterIcon =
hasPageFilter(url) ||
(model?.name && model.name !== 'unknown' && hasStoredFilter(model.name))
@@ -53,7 +61,7 @@ const DashboardBreadcrumb = () => {
return {
title: (
- {name}
+ {displayName}
),
key: segment
@@ -71,7 +79,7 @@ const DashboardBreadcrumb = () => {
gap: 6
}}
>
- {name}
+ {displayName}
{showFilterIcon && }
),
diff --git a/src/components/Dashboard/context/ObjectListViewContext.jsx b/src/components/Dashboard/context/ObjectListViewContext.jsx
index 8695e48e..c693c500 100644
--- a/src/components/Dashboard/context/ObjectListViewContext.jsx
+++ b/src/components/Dashboard/context/ObjectListViewContext.jsx
@@ -127,7 +127,7 @@ export const ObjectListViewProvider = ({
subscribeToObjectUpdates
} = useContext(ApiServerContext)
const { token, authInitialized } = useContext(AuthContext)
- const { getViewFromUrl, persistView } = useTableState()
+ const { getViewFromUrl, persistView, setObjectListView } = useTableState()
const [views, setViews] = useState(
() => readCachedViews(objectType) || []
@@ -187,6 +187,16 @@ export const ObjectListViewProvider = ({
viewsStateRef.current = { views, draftViews, isEditing }
activeViewRef.current = activeView
+ // Keep the remembered view name in sync (incl. renames while editing) for
+ // consumers outside this provider (e.g. the dashboard breadcrumb).
+ useEffect(() => {
+ if (!objectType || !activeView) return
+ setObjectListView(objectType, {
+ id: String(activeView._id),
+ name: activeView.name || null
+ })
+ }, [objectType, activeView, setObjectListView])
+
const applyViewToTable = useCallback(
(tabKey) => {
if (!tableRef?.current?.applyViewState) return
@@ -253,12 +263,26 @@ export const ObjectListViewProvider = ({
loadViewsRef.current = loadViews
const rememberActiveView = useCallback(
- (viewId) => {
- const key = viewId ? String(viewId) : null
+ (view) => {
+ const key =
+ view == null
+ ? null
+ : typeof view === 'string' || typeof view === 'number'
+ ? String(view)
+ : view?._id != null
+ ? String(view._id)
+ : null
+ const name =
+ key && view && typeof view === 'object' ? view.name || null : null
+
persistView(key)
writeLastViewId(objectType, key)
+ setObjectListView(
+ objectType,
+ key ? { id: key, name } : null
+ )
},
- [objectType, persistView]
+ [objectType, persistView, setObjectListView]
)
useEffect(() => {
@@ -327,6 +351,10 @@ export const ObjectListViewProvider = ({
const matchedId = String(matched._id)
setActiveTabKey(matchedId)
writeLastViewId(objectType, matchedId)
+ setObjectListView(objectType, {
+ id: matchedId,
+ name: matched.name || null
+ })
if (!matched._isDraft) {
// Keep viewId in the URL and strip any leftover filter/sort params
persistView(matchedId)
@@ -354,7 +382,8 @@ export const ObjectListViewProvider = ({
objectType,
getViewFromUrl,
persistView,
- rememberActiveView
+ rememberActiveView,
+ setObjectListView
])
const selectTab = useCallback(
@@ -373,7 +402,7 @@ export const ObjectListViewProvider = ({
)
if (!view || view._isDraft) return
- rememberActiveView(getObjectViewKey(view))
+ rememberActiveView(view)
},
[activeTabKey, draftViews, isEditing, rememberActiveView, views]
)
@@ -483,7 +512,7 @@ export const ObjectListViewProvider = ({
if (resolvedTabKey !== ALL_TAB_KEY && savedView && !savedView._isDraft) {
setActiveTabKey(resolvedTabKey)
- rememberActiveView(getObjectViewKey(savedView))
+ rememberActiveView(savedView)
} else if (resolvedTabKey !== ALL_TAB_KEY && !savedView) {
resolvedTabKey = ALL_TAB_KEY
setActiveTabKey(ALL_TAB_KEY)
@@ -535,7 +564,7 @@ export const ObjectListViewProvider = ({
const newKey = String(created._id)
setActiveTabKey(newKey)
applyViewToTable(newKey)
- rememberActiveView(getObjectViewKey(created))
+ rememberActiveView(created)
} else {
message.error('Failed to create view')
}
@@ -571,7 +600,7 @@ export const ObjectListViewProvider = ({
(item) => String(item._id) === String(previousKey)
)
if (view && !view._isDraft) {
- rememberActiveView(getObjectViewKey(view))
+ rememberActiveView(view)
}
}
applyViewToTable(previousKey)
@@ -665,7 +694,7 @@ export const ObjectListViewProvider = ({
(item) => String(item._id) === String(previousKey)
)
if (view && !view._isDraft) {
- rememberActiveView(getObjectViewKey(view))
+ rememberActiveView(view)
}
}
applyViewToTable(previousKey)
diff --git a/src/components/Dashboard/context/TableStateContext.jsx b/src/components/Dashboard/context/TableStateContext.jsx
index 4e5a9b94..39f6829b 100644
--- a/src/components/Dashboard/context/TableStateContext.jsx
+++ b/src/components/Dashboard/context/TableStateContext.jsx
@@ -2,6 +2,7 @@ import {
createContext,
useCallback,
useContext,
+ useEffect,
useMemo,
useRef,
useState
@@ -19,6 +20,7 @@ const getSessionFilterKey = (scope) => `tableState:${scope}:filter`
const getSessionSortKey = (scope) => `tableState:${scope}:sort`
const getSessionListFilterKey = (scope) => `tableState:${scope}:listFilter`
const getSessionListSortKey = (scope) => `tableState:${scope}:listSort`
+const getSessionListViewKey = (scope) => `tableState:${scope}:listView`
// eslint-disable-next-line react-refresh/only-export-components
export const getActiveFilterValues = (filterState) => {
@@ -128,6 +130,31 @@ const writeListSortToSession = (scope, sorter) => {
}
}
+const readListViewFromSession = (scope) => {
+ try {
+ const parsed = parseJsonParam(sessionStorage.getItem(getSessionListViewKey(scope)))
+ if (!parsed?.id) return null
+ return {
+ id: String(parsed.id),
+ name: parsed.name || null
+ }
+ } catch {
+ return null
+ }
+}
+
+const writeListViewToSession = (scope, view) => {
+ const key = getSessionListViewKey(scope)
+ if (view?.id) {
+ sessionStorage.setItem(
+ key,
+ JSON.stringify({ id: String(view.id), name: view.name || null })
+ )
+ } else {
+ sessionStorage.removeItem(key)
+ }
+}
+
const normalizeSorter = (sorter) => {
if (sorter?.field && sorter?.order) {
return { field: sorter.field, order: sorter.order }
@@ -143,16 +170,42 @@ export const TableStateProvider = ({ children }) => {
// from the list page to an info page so breadcrumb/neighbors keep working.
const [objectListFilters, setObjectListFilters] = useState({})
const [objectListSorters, setObjectListSorters] = useState({})
+ // Active list view (id + name) per object type — same survival pattern as filters.
+ const [objectListViews, setObjectListViews] = useState({})
const searchParamsRef = useRef(searchParams)
- searchParamsRef.current = searchParams
+ // Tracks an in-flight optimistic URL write so a render with stale router
+ // searchParams cannot clobber searchParamsRef and resurrect deleted params
+ // (e.g. viewId briefly cleared on All, then written back by persistTableState).
+ const pendingSearchParamsRef = useRef(null)
+ const previousSearchParamsRef = useRef(searchParams.toString())
+
+ useEffect(() => {
+ const current = searchParams.toString()
+ if (pendingSearchParamsRef.current != null) {
+ if (current === pendingSearchParamsRef.current) {
+ pendingSearchParamsRef.current = null
+ } else if (current === previousSearchParamsRef.current) {
+ // Stale snapshot from before our write — keep the optimistic ref.
+ return
+ } else {
+ // External navigation while a write was pending — accept it.
+ pendingSearchParamsRef.current = null
+ }
+ }
+ previousSearchParamsRef.current = current
+ searchParamsRef.current = searchParams
+ }, [searchParams])
// Keep sequential URL writes in the same tick from clobbering each other
// (e.g. persistView then persistTableState both calling setSearchParams).
const updateSearchParams = useCallback(
(mutator) => {
+ const previous = searchParamsRef.current.toString()
const next = new URLSearchParams(searchParamsRef.current)
mutator(next)
+ previousSearchParamsRef.current = previous
searchParamsRef.current = next
+ pendingSearchParamsRef.current = next.toString()
setSearchParams(next, { replace: true })
},
[setSearchParams]
@@ -362,6 +415,32 @@ export const TableStateProvider = ({ children }) => {
})
}, [])
+ const setObjectListView = useCallback((objectType, view) => {
+ if (!objectType) return
+ const nextView =
+ view?.id != null
+ ? { id: String(view.id), name: view.name || null }
+ : null
+ writeListViewToSession(objectType, nextView)
+ setObjectListViews((prev) => {
+ if (!nextView) {
+ if (!(objectType in prev)) return prev
+ const next = { ...prev }
+ delete next[objectType]
+ return next
+ }
+ const existing = prev[objectType]
+ if (
+ existing &&
+ existing.id === nextView.id &&
+ existing.name === nextView.name
+ ) {
+ return prev
+ }
+ return { ...prev, [objectType]: nextView }
+ })
+ }, [])
+
const getObjectListFilter = useCallback(
(objectType) => {
if (!objectType) return {}
@@ -384,6 +463,17 @@ export const TableStateProvider = ({ children }) => {
[objectListSorters]
)
+ const getObjectListView = useCallback(
+ (objectType) => {
+ if (!objectType) return null
+ if (objectListViews[objectType]) {
+ return objectListViews[objectType]
+ }
+ return readListViewFromSession(objectType)
+ },
+ [objectListViews]
+ )
+
const hasPageFilter = useCallback(
(path) => Object.keys(pageFilters[path] || {}).length > 0,
[pageFilters]
@@ -409,14 +499,17 @@ export const TableStateProvider = ({ children }) => {
setPageSorter,
setObjectListFilter,
setObjectListSorter,
+ setObjectListView,
getObjectListFilter,
getObjectListSorter,
+ getObjectListView,
hasPageFilter,
hasStoredFilter,
pageFilters,
pageSorters,
objectListFilters,
- objectListSorters
+ objectListSorters,
+ objectListViews
}),
[
getPersistedFilter,
@@ -430,14 +523,17 @@ export const TableStateProvider = ({ children }) => {
setPageSorter,
setObjectListFilter,
setObjectListSorter,
+ setObjectListView,
getObjectListFilter,
getObjectListSorter,
+ getObjectListView,
hasPageFilter,
hasStoredFilter,
pageFilters,
pageSorters,
objectListFilters,
- objectListSorters
+ objectListSorters,
+ objectListViews
]
)