From 26d8c80d54533a2efc9e3f0648031a5fb2eb76dc Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Thu, 3 Sep 2026 02:58:29 +0100 Subject: [PATCH] Enhance ObjectKanban with Skeleton Loading and Responsive Layout - Introduced a skeleton loading state in the ObjectKanban component to improve user experience during data fetching. - Updated CSS styles for the ObjectKanban and related components to ensure a responsive layout, accommodating various screen sizes. - Refactored ObjectKanbanHeader to conditionally render skeletons based on loading state, enhancing visual feedback. - Improved filter and sorter handling in ObjectKanbanColumn for better data management during loading. --- assets/stylesheets/App.css | 22 ++ .../Dashboard/common/ObjectKanban.jsx | 300 +++++++++++++----- .../Dashboard/common/ObjectKanbanColumn.jsx | 17 +- .../Dashboard/common/ObjectKanbanHeader.jsx | 51 ++- .../Dashboard/common/ObjectTable.jsx | 168 ++++++---- .../common/ObjectTableViewButton.jsx | 2 +- 6 files changed, 394 insertions(+), 166 deletions(-) diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index 24d3aabb..f95d3f28 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -1372,16 +1372,29 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { width: 100%; height: 100%; min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; } +.objectTableCardsContainer > .ant-spin-nested-loading, +.objectTableCardsContainer > .ant-spin-nested-loading > .ant-spin-container, .objectTableCardsContainer .ant-spin-nested-loading { width: 100%; height: 100%; + min-height: 0; + flex: 1; + display: flex; + flex-direction: column; } .objectTableCardsContainer .ant-spin-container { width: 100%; height: 100%; + min-height: 0; + flex: 1; + display: flex; + flex-direction: column; } .objectKanbanContainerWrapper { @@ -1394,6 +1407,15 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { flex-direction: column; } +.objectKanbanContainerWrapper > .ant-spin-nested-loading, +.objectKanbanContainerWrapper > .ant-spin-nested-loading > .ant-spin-container { + height: 100%; + min-height: 0; + flex: 1; + display: flex; + flex-direction: column; +} + .objectKanbanContainerSkeletons { display: flex; flex-direction: column; diff --git a/src/components/Dashboard/common/ObjectKanban.jsx b/src/components/Dashboard/common/ObjectKanban.jsx index 851a87f0..16648892 100644 --- a/src/components/Dashboard/common/ObjectKanban.jsx +++ b/src/components/Dashboard/common/ObjectKanban.jsx @@ -8,14 +8,91 @@ import { useRef, useState } from 'react' -import { Empty, Flex } from 'antd' +import { Empty, Flex, Spin } from 'antd' +import { LoadingOutlined } from '@ant-design/icons' import PropTypes from 'prop-types' import { ApiServerContext } from '../context/ApiServerContext' +import ObjectCard from './ObjectCard' import ObjectKanbanColumn from './ObjectKanbanColumn' import ObjectKanbanHeader from './ObjectKanbanHeader' import ScrollBox from './ScrollBox' import { getCategoryValueKey } from './viewModeUtils' +const KANBAN_COLUMN_WIDTH = 360 +const KANBAN_GAP = 16 +const KANBAN_CARD_MIN_HEIGHT = 136 + +const getKanbanSkeletonLayout = (width, height) => { + const columnCount = + Math.max( + 1, + Math.floor((width + KANBAN_GAP) / (KANBAN_COLUMN_WIDTH + KANBAN_GAP)) + ) + 1 + const cardCount = Math.max( + 1, + Math.floor((height - 32) / KANBAN_CARD_MIN_HEIGHT) + ) + + return { columnCount, cardCount } +} + +const ObjectKanbanSkeletonColumns = ({ + columnCount, + cardCount = 0, + trackRef, + model, + modelProperties, + visibleColumns, + keyPrefix = 'skeleton' +}) => ( +
+
+ + {Array.from({ length: columnCount }).map((_, columnIndex) => ( +
+
+ {cardCount > 0 && ( + + {Array.from({ length: cardCount }).map((_, cardIndex) => ( + + ))} + + )} +
+
+ ))} +
+
+
+) + +ObjectKanbanSkeletonColumns.propTypes = { + columnCount: PropTypes.number.isRequired, + cardCount: PropTypes.number, + trackRef: PropTypes.object, + model: PropTypes.object.isRequired, + modelProperties: PropTypes.array.isRequired, + visibleColumns: PropTypes.object, + keyPrefix: PropTypes.string +} + const ObjectKanban = forwardRef( ( { @@ -38,15 +115,24 @@ const ObjectKanban = forwardRef( const columnRefs = useRef({}) const headerTrackRef = useRef(null) const skeletonTrackRef = useRef(null) + const kanbanBodyRef = useRef(null) const [scrollElement, setScrollElement] = useState(null) const [categoryValues, setCategoryValues] = useState([]) const [hasLoaded, setHasLoaded] = useState(false) + const [loadedFilter, setLoadedFilter] = useState(baseFilter) + const [loadedSorter, setLoadedSorter] = useState(sorter) + const [skeletonLayout, setSkeletonLayout] = useState({ + columnCount: 3, + cardCount: 3 + }) const baseFilterRef = useRef(baseFilter) const masterFilterRef = useRef(masterFilter) const sorterRef = useRef(sorter) const getModelPropertyValuesRef = useRef(getModelPropertyValues) const lastCategoryQueryKeyRef = useRef(null) + const hasLoadedRef = useRef(false) + const loadGenerationRef = useRef(0) baseFilterRef.current = baseFilter masterFilterRef.current = masterFilter @@ -57,6 +143,23 @@ const ObjectKanban = forwardRef( setScrollElement(node) }, []) + useLayoutEffect(() => { + const kanbanBody = kanbanBodyRef.current + if (!kanbanBody) return undefined + + const updateLayout = () => { + const { width, height } = kanbanBody.getBoundingClientRect() + setSkeletonLayout(getKanbanSkeletonLayout(width, height)) + } + + updateLayout() + + const observer = new ResizeObserver(updateLayout) + observer.observe(kanbanBody) + + return () => observer.disconnect() + }, [categoryProperty]) + useLayoutEffect(() => { const mainScroll = scrollElement const headerTrack = headerTrackRef.current @@ -109,8 +212,11 @@ const ObjectKanban = forwardRef( const load = useCallback( async (filter = null, sorterArg = null) => { + const generation = ++loadGenerationRef.current + if (!categoryProperty) { lastCategoryQueryKeyRef.current = null + hasLoadedRef.current = true setCategoryValues([]) setHasLoaded(true) return @@ -118,14 +224,21 @@ const ObjectKanban = forwardRef( const activeFilter = filter ?? baseFilterRef.current const activeSorter = sorterArg ?? sorterRef.current + const activeMasterFilter = masterFilterRef.current const queryKey = buildCategoryQueryKey(activeFilter, activeSorter) - if (lastCategoryQueryKeyRef.current === queryKey) { + const canReuseColumns = + lastCategoryQueryKeyRef.current === queryKey && hasLoadedRef.current + + if (canReuseColumns) { await reloadColumns() + if (generation !== loadGenerationRef.current) return + hasLoadedRef.current = true setHasLoaded(true) return } + hasLoadedRef.current = false setHasLoaded(false) try { @@ -134,19 +247,25 @@ const ObjectKanban = forwardRef( categoryProperty, { filter: activeFilter, - masterFilter: masterFilterRef.current + masterFilter: activeMasterFilter } ) + if (generation !== loadGenerationRef.current) return const nextValues = Array.isArray(values) ? values : [] - setCategoryValues(nextValues) lastCategoryQueryKeyRef.current = queryKey - } catch (error) { - console.error('Error fetching kanban category values:', error) - setCategoryValues([]) - lastCategoryQueryKeyRef.current = null - throw error - } finally { + hasLoadedRef.current = true + setLoadedFilter(activeFilter) + setLoadedSorter(activeSorter) + setCategoryValues(nextValues) setHasLoaded(true) + } catch (error) { + if (generation !== loadGenerationRef.current) return + console.error('Error fetching kanban category values:', error) + lastCategoryQueryKeyRef.current = null + hasLoadedRef.current = true + setCategoryValues([]) + setHasLoaded(true) + throw error } }, [buildCategoryQueryKey, categoryProperty, reloadColumns, type] @@ -154,6 +273,7 @@ const ObjectKanban = forwardRef( const reload = useCallback(async () => { lastCategoryQueryKeyRef.current = null + hasLoadedRef.current = false await load(baseFilterRef.current, sorterRef.current) }, [load]) @@ -182,82 +302,100 @@ const ObjectKanban = forwardRef( ) } - if (!hasLoaded || categoryValues.length === 0) { - return ( -
- ) - } + const showSkeleton = !hasLoaded return (
-
- -
-
- - - {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) => (