-
-
-
-
-
- {categoryValues.map((categoryValue) => {
- const columnKey = getCategoryValueKey(categoryValue)
- return (
- {
- if (node) {
- columnRefs.current[columnKey] = node
- } else {
- delete columnRefs.current[columnKey]
- }
- }}
- scrollElement={scrollElement}
- type={type}
- categoryProperty={categoryProperty}
- categoryValue={categoryValue}
- baseFilter={baseFilter}
- masterFilter={masterFilter}
- sorter={sorter}
- pageSize={pageSize}
- model={model}
- modelProperties={modelProperties}
- visibleColumns={visibleColumns}
- isEditing={isEditing}
- rowActions={rowActions}
- renderActions={renderActions}
- />
- )
- })}
-
-
-
-
-
-
- {categoryValues.map((categoryValue) => {
- const columnKey = getCategoryValueKey(categoryValue)
- return (
-
- )
- })}
-
-
+
}
+ spinning={showSkeleton}
+ style={{ height: '100%', flex: 1, minHeight: 0 }}
+ >
+
+
+
+ {!showSkeleton && (
+
+
+
+ {categoryValues.map((categoryValue) => {
+ const columnKey = getCategoryValueKey(categoryValue)
+ return (
+ {
+ if (node) {
+ columnRefs.current[columnKey] = node
+ } else {
+ delete columnRefs.current[columnKey]
+ }
+ }}
+ scrollElement={scrollElement}
+ type={type}
+ categoryProperty={categoryProperty}
+ categoryValue={categoryValue}
+ baseFilter={loadedFilter}
+ masterFilter={masterFilter}
+ sorter={loadedSorter}
+ pageSize={pageSize}
+ model={model}
+ modelProperties={modelProperties}
+ visibleColumns={visibleColumns}
+ isEditing={isEditing}
+ rowActions={rowActions}
+ renderActions={renderActions}
+ />
+ )
+ })}
+
+
+
+ )}
+ {showSkeleton ? (
+
+ ) : (
+
+
+
+ {categoryValues.map((categoryValue) => {
+ const columnKey = getCategoryValueKey(categoryValue)
+ return (
+
+ )
+ })}
+
+
+
+ )}
-
+
)
}
diff --git a/src/components/Dashboard/common/ObjectKanbanColumn.jsx b/src/components/Dashboard/common/ObjectKanbanColumn.jsx
index 6e842c84..ae544e1f 100644
--- a/src/components/Dashboard/common/ObjectKanbanColumn.jsx
+++ b/src/components/Dashboard/common/ObjectKanbanColumn.jsx
@@ -164,17 +164,18 @@ const ObjectKanbanColumn = forwardRef(
)
const fetchPage = useCallback(
- async (pageNum) => {
- const filter = {
+ async (pageNum, overrides = {}) => {
+ const filter = overrides.filter ?? {
...masterFilterRef.current,
...baseFilterRef.current,
[categoryProperty]: toCategoryFilterValue(categoryValue)
}
+ const sorter = overrides.sorter ?? sorterRef.current
return fetchObjectsRef.current(type, {
page: pageNum,
limit: pageSize,
filter,
- sorter: sorterRef.current
+ sorter
})
},
[categoryProperty, categoryValue, pageSize, type]
@@ -362,6 +363,12 @@ const ObjectKanbanColumn = forwardRef(
loadingPagesRef.current.clear()
pendingScrollAnchorRef.current = null
const generation = dataLoadGenerationRef.current
+ const filter = {
+ ...masterFilterRef.current,
+ ...baseFilterRef.current,
+ [categoryProperty]: toCategoryFilterValue(categoryValue)
+ }
+ const sorter = sorterRef.current
pagesRef.current = []
setPages([])
@@ -371,7 +378,7 @@ const ObjectKanbanColumn = forwardRef(
setTablePages([skeletonPage])
try {
- const firstResult = await fetchPage(1)
+ const firstResult = await fetchPage(1, { filter, sorter })
if (isStaleDataLoad(generation)) return
const loadedPages = [
{
@@ -393,6 +400,8 @@ const ObjectKanbanColumn = forwardRef(
}
}
}, [
+ categoryProperty,
+ categoryValue,
createPageWindow,
createSkeletonPage,
fetchPage,
diff --git a/src/components/Dashboard/common/ObjectKanbanHeader.jsx b/src/components/Dashboard/common/ObjectKanbanHeader.jsx
index fd4836b0..e9c735fe 100644
--- a/src/components/Dashboard/common/ObjectKanbanHeader.jsx
+++ b/src/components/Dashboard/common/ObjectKanbanHeader.jsx
@@ -1,4 +1,4 @@
-import { Flex } from 'antd'
+import { Flex, Skeleton } from 'antd'
import PropTypes from 'prop-types'
import ObjectProperty from './ObjectProperty'
import { getCategoryValueKey } from './viewModeUtils'
@@ -7,27 +7,43 @@ const ObjectKanbanHeader = ({
categoryValues,
categoryProperty,
categoryPropertyDef,
- trackRef
+ trackRef,
+ skeletonColumnCount = 0
}) => {
+ const showSkeleton = skeletonColumnCount > 0
+
return (
- {categoryValues.map((categoryValue) => {
- const columnKey = getCategoryValueKey(categoryValue)
- return (
-
- {categoryPropertyDef ? (
-
(
+
+
- ) : null}
-
- )
- })}
+
+ ))
+ : categoryValues.map((categoryValue) => {
+ const columnKey = getCategoryValueKey(categoryValue)
+ return (
+
+ {categoryPropertyDef ? (
+
+ ) : null}
+
+ )
+ })}
@@ -40,7 +56,8 @@ ObjectKanbanHeader.propTypes = {
categoryValues: PropTypes.array.isRequired,
categoryProperty: PropTypes.string.isRequired,
categoryPropertyDef: PropTypes.object,
- trackRef: PropTypes.object
+ trackRef: PropTypes.object,
+ skeletonColumnCount: PropTypes.number
}
export default ObjectKanbanHeader
diff --git a/src/components/Dashboard/common/ObjectTable.jsx b/src/components/Dashboard/common/ObjectTable.jsx
index 56116f8a..77fabb26 100644
--- a/src/components/Dashboard/common/ObjectTable.jsx
+++ b/src/components/Dashboard/common/ObjectTable.jsx
@@ -66,6 +66,7 @@ logger.setLevel(config.logLevel)
const SCROLL_THRESHOLD = 50
const SKELETON_HEIGHT = 49.5
+const EMPTY_MASTER_FILTER = {}
const getCardColSpan = (containerWidth) => {
if (containerWidth >= 2980) return 2
@@ -275,7 +276,7 @@ const ObjectTable = forwardRef(
viewMode: viewModeProp,
cards = false,
visibleColumns = {},
- masterFilter = {},
+ masterFilter,
size = 'middle',
onStateChange,
showFilterSidebar = false,
@@ -303,10 +304,11 @@ const ObjectTable = forwardRef(
const isCards = isCardsView(viewMode)
const isKanban = isKanbanView(viewMode)
const kanbanRef = useRef(null)
- const wasKanbanRef = useRef(false)
+ const prevViewModeKeyRef = useRef(null)
const { token, userProfile } = useContext(AuthContext)
const { isElectron } = useContext(ElectronContext)
const { callAction } = useActions()
+ const resolvedMasterFilter = masterFilter ?? EMPTY_MASTER_FILTER
const listViewId = objectListView?.listViewId ?? null
const listViewFilter = objectListView?.listViewFilter ?? null
const listViewSort = objectListView?.listViewSort ?? null
@@ -378,6 +380,7 @@ const ObjectTable = forwardRef(
const tableSorterRef = useRef({})
const listViewIdRef = useRef(listViewId)
const listViewFilterRef = useRef(listViewFilter)
+ const sidebarViewIdRef = useRef(listViewId)
const activeObjectViewRef = useRef(activeObjectView)
activeObjectViewRef.current = activeObjectView
const onObjectViewFilterSortChangeRef = useRef(onObjectViewFilterSortChange)
@@ -393,13 +396,19 @@ const ObjectTable = forwardRef(
listViewFilterRef.current = listViewFilter || {}
}, [listViewId, listViewFilter])
+ const assignSidebarToView = useCallback((viewId) => {
+ sidebarViewIdRef.current = viewId ?? null
+ }, [])
+
const lastAppliedViewKeyRef = useRef(null)
const buildEffectiveFilter = useCallback(
(userFilter, viewFilter = listViewFilterRef.current) => {
const active = getActiveFilterValues(userFilter)
const viewId = listViewIdRef.current
- if (!viewId) return active
+ // While editing a view, the sidebar IS the view filter. Merging the
+ // previous definition would put removed keys back.
+ if (!viewId || activeObjectViewRef.current) return active
return {
...getActiveFilterValues(viewFilter || {}),
...active
@@ -455,11 +464,11 @@ const ObjectTable = forwardRef(
const subscribeToObjectTypeUpdatesFnRef = useRef(
subscribeToObjectTypeUpdates
)
- const prevValuesRef = useRef({ type, masterFilter })
+ const prevValuesRef = useRef({ type, masterFilter: resolvedMasterFilter })
const getMasterFilter = useCallback(
- () => masterFilter || {},
- [masterFilter]
+ () => resolvedMasterFilter || EMPTY_MASTER_FILTER,
+ [resolvedMasterFilter]
)
const resolveSorter = useCallback((userSorter) => {
@@ -1042,15 +1051,26 @@ const ObjectTable = forwardRef(
// view definition + any user-entered sidebar filters.
const effectiveFilter = useMemo(() => {
const userFilter = getActiveFilterValues(sidebarFilter)
- if (!listViewId) return userFilter
+ const sidebarMatchesView = sidebarViewIdRef.current === listViewId
+
+ if (!listViewId || activeObjectView) {
+ if (!listViewId && !sidebarMatchesView) return {}
+ return userFilter
+ }
+
+ const viewFilter = getActiveFilterValues(listViewFilter || {})
+ if (!sidebarMatchesView) {
+ return viewFilter
+ }
return {
- ...getActiveFilterValues(listViewFilter || {}),
+ ...viewFilter,
...userFilter
}
- }, [listViewId, listViewFilter, sidebarFilter])
+ }, [activeObjectView, listViewId, listViewFilter, sidebarFilter])
const effectiveSorter = useMemo(() => {
- if (tableSorter?.field && tableSorter?.order) {
+ const sorterMatchesView = sidebarViewIdRef.current === listViewId
+ if (sorterMatchesView && tableSorter?.field && tableSorter?.order) {
return { field: tableSorter.field, order: tableSorter.order }
}
if (listViewId && listViewSort?.field && listViewSort?.order) {
@@ -1060,16 +1080,16 @@ const ObjectTable = forwardRef(
}, [listViewId, listViewSort, tableSorter])
const subscriptionFilter = useMemo(
- () => ({ ...masterFilter, ...effectiveFilter }),
- [effectiveFilter, masterFilter]
+ () => ({ ...resolvedMasterFilter, ...effectiveFilter }),
+ [effectiveFilter, resolvedMasterFilter]
)
const tableFilterContextValue = useMemo(
() => ({
filter: effectiveFilter,
- masterFilter
+ masterFilter: resolvedMasterFilter
}),
- [effectiveFilter, masterFilter]
+ [effectiveFilter, resolvedMasterFilter]
)
newEventHandlerRef.current = newEventHandler
@@ -1142,27 +1162,33 @@ const ObjectTable = forwardRef(
const loadPage = useCallback(
async (pageNum, filter = null, sorter = null) => {
if (isKanban) {
+ const generation = dataLoadGenerationRef.current + 1
+ dataLoadGenerationRef.current = generation
+ const activeFilter =
+ filter != null ? filter : activeFilterRef.current
+ const resolvedSorter =
+ sorter != null
+ ? resolveSorter(sorter)
+ : resolveSorter(tableSorterRef.current)
+ const sorterForLoad = resolvedSorter?.field ? resolvedSorter : {}
+
setLoading(true)
try {
- const activeFilter =
- filter != null ? filter : activeFilterRef.current
- const resolvedSorter =
- sorter != null
- ? resolveSorter(sorter)
- : resolveSorter(tableSorterRef.current)
-
- if (!kanbanRef.current?.load) {
+ let loadKanban = kanbanRef.current?.load
+ if (!loadKanban) {
await new Promise((resolve) => requestAnimationFrame(resolve))
+ if (generation !== dataLoadGenerationRef.current) return
+ loadKanban = kanbanRef.current?.load
}
- await kanbanRef.current?.load?.(
- activeFilter,
- resolvedSorter?.field ? resolvedSorter : {}
- )
+ if (!loadKanban) return
+ await loadKanban(activeFilter, sorterForLoad)
} catch (error) {
logger.error('Error loading kanban view:', error)
} finally {
- setLoading(false)
+ if (generation === dataLoadGenerationRef.current) {
+ setLoading(false)
+ }
}
return
}
@@ -1229,6 +1255,7 @@ const ObjectTable = forwardRef(
const resolvedSorter = resolveSorter(sorter)
if (Object.keys(filter).length > 0) {
+ assignSidebarToView(listViewIdRef.current)
setSidebarFilter(filter)
}
if (sorter?.field) {
@@ -1242,6 +1269,7 @@ const ObjectTable = forwardRef(
resolvedSorter.field ? resolvedSorter : null
)
}, [
+ assignSidebarToView,
getPersistedFilter,
getPersistedSorter,
getViewFromUrl,
@@ -1249,6 +1277,8 @@ const ObjectTable = forwardRef(
loadPage,
resolveSorter
])
+ const loadInitialPageRef = useRef(loadInitialPage)
+ loadInitialPageRef.current = loadInitialPage
// When a view becomes available, apply its definition once. Skip empty
// prop-lag updates so we never clear an already-applied view filter.
@@ -1275,6 +1305,7 @@ const ObjectTable = forwardRef(
lastAppliedViewKeyRef.current = viewKey
+ assignSidebarToView(listViewId)
setSidebarFilter(viewFilter)
setTableSorter(viewSort)
tableSorterRef.current = viewSort
@@ -1307,6 +1338,7 @@ const ObjectTable = forwardRef(
listViewIdRef.current = null
listViewFilterRef.current = {}
lastAppliedViewKeyRef.current = null
+ assignSidebarToView(null)
filter = getAllTabPersistedFilter()
sorter = getAllTabPersistedSorter()
@@ -1352,6 +1384,7 @@ const ObjectTable = forwardRef(
lastAppliedViewKeyRef.current = `${viewId || ''}:${JSON.stringify(activeFilter)}:${JSON.stringify(nextSorter)}`
+ assignSidebarToView(viewId)
setSidebarFilter(filter)
tableSorterRef.current = nextSorter
setTableSorter(nextSorter)
@@ -1364,6 +1397,7 @@ const ObjectTable = forwardRef(
)
},
[
+ assignSidebarToView,
getAllTabPersistedFilter,
getAllTabPersistedSorter,
getViewFromUrl,
@@ -1396,7 +1430,7 @@ const ObjectTable = forwardRef(
let cancelled = false
const run = async () => {
- await loadInitialPage()
+ await loadInitialPageRef.current()
if (!cancelled) {
setInitialized(true)
}
@@ -1407,35 +1441,34 @@ const ObjectTable = forwardRef(
return () => {
cancelled = true
}
- }, [token, loadInitialPage, initialized, connected])
+ }, [token, initialized, connected])
useEffect(() => {
- if (!isKanban || connected !== true || token == null) {
- wasKanbanRef.current = isKanban
+ const viewModeKey = `${viewMode.type}:${viewMode?.settings?.categoryProperty || ''}`
+
+ if (connected !== true || token == null || !initialized) {
+ prevViewModeKeyRef.current = viewModeKey
return
}
- const categoryProperty = viewMode?.settings?.categoryProperty
- if (!categoryProperty) {
- wasKanbanRef.current = isKanban
- return
- }
+ if (prevViewModeKeyRef.current === viewModeKey) return
+ prevViewModeKeyRef.current = viewModeKey
- if (!wasKanbanRef.current) {
- loadPage(
- initialPage,
- activeFilterRef.current,
- tableSorterRef.current?.field ? tableSorterRef.current : null
- )
- }
+ if (isKanban && !viewMode?.settings?.categoryProperty) return
- wasKanbanRef.current = isKanban
+ loadPage(
+ initialPage,
+ activeFilterRef.current,
+ tableSorterRef.current?.field ? tableSorterRef.current : null
+ )
}, [
isKanban,
connected,
token,
+ initialized,
loadPage,
initialPage,
+ viewMode.type,
viewMode?.settings?.categoryProperty
])
@@ -1445,7 +1478,8 @@ const ObjectTable = forwardRef(
const hasChanged =
prevValues.type !== type ||
- JSON.stringify(prevValues.masterFilter) !== JSON.stringify(masterFilter)
+ JSON.stringify(prevValues.masterFilter) !==
+ JSON.stringify(resolvedMasterFilter)
if (hasChanged) {
beginDataReload()
@@ -1453,14 +1487,15 @@ const ObjectTable = forwardRef(
setPages([])
activeFilterRef.current = {}
tableSorterRef.current = {}
+ assignSidebarToView(null)
setSidebarFilter({})
setTableSorter({})
setInitialized(false)
setLoading(true)
setLazyLoading(false)
- prevValuesRef.current = { type, masterFilter }
+ prevValuesRef.current = { type, masterFilter: resolvedMasterFilter }
}
- }, [type, masterFilter, beginDataReload])
+ }, [type, resolvedMasterFilter, beginDataReload])
useEffect(() => {
registerPageFilter(effectiveFilter)
@@ -1493,7 +1528,7 @@ const ObjectTable = forwardRef(
propertyLabel={propertyLabel}
modelType={type}
filter={effectiveFilter}
- masterFilter={masterFilter}
+ masterFilter={resolvedMasterFilter}
/>
)
@@ -1517,25 +1552,27 @@ const ObjectTable = forwardRef(
}
setSidebarFilter(next)
+ assignSidebarToView(listViewIdRef.current)
setTableSorter(nextSorter)
- const effective = buildEffectiveFilter(next)
- activeFilterRef.current = effective
if (activeObjectViewRef.current) {
+ listViewFilterRef.current = next
// Editing a view: keep filter/sort on the draft view only
onObjectViewFilterSortChangeRef.current?.(next, nextSorter)
} else if (!listViewIdRef.current) {
// All tab: persist personal filter/sort to URL/session
persistTableState(next, nextSorter)
}
+ const effective = buildEffectiveFilter(next)
+ activeFilterRef.current = effective
loadPage(initialPage, effective, resolveSorter(nextSorter))
}
const handleSidebarFilterChange = useCallback(
(newSidebarFilter) => {
setSidebarFilter(newSidebarFilter)
- const effective = buildEffectiveFilter(newSidebarFilter)
- activeFilterRef.current = effective
+ assignSidebarToView(listViewIdRef.current)
if (activeObjectViewRef.current) {
+ listViewFilterRef.current = newSidebarFilter
onObjectViewFilterSortChangeRef.current?.(
newSidebarFilter,
tableSorter
@@ -1543,9 +1580,12 @@ const ObjectTable = forwardRef(
} else if (!listViewIdRef.current) {
persistFilter(newSidebarFilter)
}
+ const effective = buildEffectiveFilter(newSidebarFilter)
+ activeFilterRef.current = effective
loadPage(initialPage, effective, resolveSorter(tableSorter))
},
[
+ assignSidebarToView,
buildEffectiveFilter,
initialPage,
loadPage,
@@ -1568,11 +1608,8 @@ const ObjectTable = forwardRef(
} else if (!listViewIdRef.current) {
persistSort(nextSorter)
}
- loadPage(
- initialPage,
- buildEffectiveFilter(sidebarFilter),
- resolveSorter(nextSorter)
- )
+ const effective = buildEffectiveFilter(sidebarFilter)
+ loadPage(initialPage, effective, resolveSorter(nextSorter))
},
[
buildEffectiveFilter,
@@ -1711,7 +1748,10 @@ const ObjectTable = forwardRef(
}
}
- if (isFilterable && !Object.keys(masterFilter).includes(prop.name)) {
+ if (
+ isFilterable &&
+ !Object.keys(resolvedMasterFilter).includes(prop.name)
+ ) {
columnConfig.filterDropdown = ({
setSelectedKeys,
selectedKeys,
@@ -1851,7 +1891,9 @@ const ObjectTable = forwardRef(
return (
@@ -1944,7 +1986,7 @@ const ObjectTable = forwardRef(
type={type}
categoryProperty={viewMode.settings?.categoryProperty}
baseFilter={effectiveFilter}
- masterFilter={masterFilter}
+ masterFilter={resolvedMasterFilter}
sorter={effectiveSorter}
pageSize={pageSize}
model={model}
@@ -1964,7 +2006,7 @@ const ObjectTable = forwardRef(
}
spinning={loading}
- style={{ height: '100%' }}
+ style={{ height: '100%', flex: 1, minHeight: 0 }}
>
{renderCards()}
@@ -2012,9 +2054,9 @@ const ObjectTable = forwardRef(
)}
diff --git a/src/components/Dashboard/common/ObjectTableViewButton.jsx b/src/components/Dashboard/common/ObjectTableViewButton.jsx
index 76941505..c8983b5c 100644
--- a/src/components/Dashboard/common/ObjectTableViewButton.jsx
+++ b/src/components/Dashboard/common/ObjectTableViewButton.jsx
@@ -119,7 +119,7 @@ const ObjectTableViewButton = ({
).map((option) => (