Enhance ObjectKanban and ObjectTable with Silent Loading Options
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Updated ObjectKanban and ObjectTable components to support silent loading, allowing for smoother user experience during data fetching without disrupting the UI.
- Refactored load functions to accept an options parameter, enabling conditional loading states based on the silent flag.
- Improved loading state management in ObjectKanbanColumn to maintain visual consistency during filter and sort updates.
- Adjusted loading behavior in ObjectTable to prevent unnecessary loading indicators when silent loading is enabled.
This commit is contained in:
Tom Butcher 2026-09-03 03:17:38 +01:00
parent 26d8c80d54
commit 1466fe0f62
3 changed files with 87 additions and 23 deletions

View File

@ -211,7 +211,8 @@ const ObjectKanban = forwardRef(
}, []) }, [])
const load = useCallback( const load = useCallback(
async (filter = null, sorterArg = null) => { async (filter = null, sorterArg = null, options = {}) => {
const silent = options.silent === true
const generation = ++loadGenerationRef.current const generation = ++loadGenerationRef.current
if (!categoryProperty) { if (!categoryProperty) {
@ -238,8 +239,12 @@ const ObjectKanban = forwardRef(
return return
} }
hasLoadedRef.current = false // Filter/sort changes keep current columns visible; objectView /
setHasLoaded(false) // initial loads clear to skeleton + Spin.
if (!silent || !hasLoadedRef.current) {
hasLoadedRef.current = false
setHasLoaded(false)
}
try { try {
const values = await getModelPropertyValuesRef.current( const values = await getModelPropertyValuesRef.current(

View File

@ -358,7 +358,7 @@ const ObjectKanbanColumn = forwardRef(
[loadBoundaryPage] [loadBoundaryPage]
) )
const loadInitialPage = useCallback(async () => { const loadInitialPage = useCallback(async ({ silent = false } = {}) => {
dataLoadGenerationRef.current += 1 dataLoadGenerationRef.current += 1
loadingPagesRef.current.clear() loadingPagesRef.current.clear()
pendingScrollAnchorRef.current = null pendingScrollAnchorRef.current = null
@ -370,12 +370,16 @@ const ObjectKanbanColumn = forwardRef(
} }
const sorter = sorterRef.current const sorter = sorterRef.current
pagesRef.current = [] if (!silent) {
setPages([]) pagesRef.current = []
setLoading(true) setPages([])
setLoading(true)
const skeletonPage = createSkeletonPage(1) const skeletonPage = createSkeletonPage(1)
setTablePages([skeletonPage]) setTablePages([skeletonPage])
} else {
setLoading(false)
}
try { try {
const firstResult = await fetchPage(1, { filter, sorter }) const firstResult = await fetchPage(1, { filter, sorter })
@ -391,11 +395,11 @@ const ObjectKanbanColumn = forwardRef(
setTablePages(createPageWindow(loadedPages, 'next')) setTablePages(createPageWindow(loadedPages, 'next'))
} }
} catch { } catch {
if (!isStaleDataLoad(generation)) { if (!silent && !isStaleDataLoad(generation)) {
setTablePages([]) setTablePages([])
} }
} finally { } finally {
if (!isStaleDataLoad(generation)) { if (!silent && !isStaleDataLoad(generation)) {
setLoading(false) setLoading(false)
} }
} }
@ -425,10 +429,12 @@ const ObjectKanbanColumn = forwardRef(
useEffect(() => { useEffect(() => {
if (lastColumnQueryKeyRef.current === columnQueryKey) return if (lastColumnQueryKeyRef.current === columnQueryKey) return
// First mount shows skeleton; later filter/sort updates load silently.
const silent = lastColumnQueryKeyRef.current != null
let cancelled = false let cancelled = false
const runLoad = async () => { const runLoad = async () => {
await loadInitialPage() await loadInitialPage({ silent })
if (!cancelled) { if (!cancelled) {
lastColumnQueryKeyRef.current = columnQueryKey lastColumnQueryKeyRef.current = columnQueryKey
} }

View File

@ -1160,19 +1160,24 @@ const ObjectTable = forwardRef(
) )
const loadPage = useCallback( const loadPage = useCallback(
async (pageNum, filter = null, sorter = null) => { async (pageNum, filter = null, sorter = null, options = {}) => {
const silent = options.silent === true
if (isKanban) { if (isKanban) {
const generation = dataLoadGenerationRef.current + 1 const generation = dataLoadGenerationRef.current + 1
dataLoadGenerationRef.current = generation dataLoadGenerationRef.current = generation
const activeFilter = const activeFilter = filter != null ? filter : activeFilterRef.current
filter != null ? filter : activeFilterRef.current
const resolvedSorter = const resolvedSorter =
sorter != null sorter != null
? resolveSorter(sorter) ? resolveSorter(sorter)
: resolveSorter(tableSorterRef.current) : resolveSorter(tableSorterRef.current)
const sorterForLoad = resolvedSorter?.field ? resolvedSorter : {} const sorterForLoad = resolvedSorter?.field ? resolvedSorter : {}
setLoading(true) if (!silent) {
setLoading(true)
} else {
setLoading(false)
}
try { try {
let loadKanban = kanbanRef.current?.load let loadKanban = kanbanRef.current?.load
if (!loadKanban) { if (!loadKanban) {
@ -1182,16 +1187,47 @@ const ObjectTable = forwardRef(
} }
if (!loadKanban) return if (!loadKanban) return
await loadKanban(activeFilter, sorterForLoad) await loadKanban(activeFilter, sorterForLoad, { silent })
} catch (error) { } catch (error) {
logger.error('Error loading kanban view:', error) logger.error('Error loading kanban view:', error)
} finally { } finally {
if (generation === dataLoadGenerationRef.current) { if (!silent && generation === dataLoadGenerationRef.current) {
setLoading(false) setLoading(false)
} }
} }
return return
} }
if (silent) {
const generation = dataLoadGenerationRef.current + 1
dataLoadGenerationRef.current = generation
loadingPagesRef.current.clear()
pendingScrollAnchorRef.current = null
setLoading(false)
try {
const firstResult = await fetchPage(pageNum, filter, sorter)
if (isStaleDataLoad(generation)) return
const loadedPages = [
{
pageNum,
items: firstResult.data || [],
hasMore: firstResult.hasMore
}
]
if (!isStaleDataLoad(generation)) {
setTablePages(createPageWindow(loadedPages, 'next'))
suppressTableChange()
}
} catch (error) {
if (!isStaleDataLoad(generation)) {
logger.error(`Error loading page ${pageNum}:`, error)
}
}
return
}
const generation = beginDataReload() const generation = beginDataReload()
clearTablePages() clearTablePages()
if (isStaleDataLoad(generation)) return if (isStaleDataLoad(generation)) return
@ -1564,7 +1600,9 @@ const ObjectTable = forwardRef(
} }
const effective = buildEffectiveFilter(next) const effective = buildEffectiveFilter(next)
activeFilterRef.current = effective activeFilterRef.current = effective
loadPage(initialPage, effective, resolveSorter(nextSorter)) loadPage(initialPage, effective, resolveSorter(nextSorter), {
silent: true
})
} }
const handleSidebarFilterChange = useCallback( const handleSidebarFilterChange = useCallback(
@ -1582,7 +1620,9 @@ const ObjectTable = forwardRef(
} }
const effective = buildEffectiveFilter(newSidebarFilter) const effective = buildEffectiveFilter(newSidebarFilter)
activeFilterRef.current = effective activeFilterRef.current = effective
loadPage(initialPage, effective, resolveSorter(tableSorter)) loadPage(initialPage, effective, resolveSorter(tableSorter), {
silent: true
})
}, },
[ [
assignSidebarToView, assignSidebarToView,
@ -1609,7 +1649,9 @@ const ObjectTable = forwardRef(
persistSort(nextSorter) persistSort(nextSorter)
} }
const effective = buildEffectiveFilter(sidebarFilter) const effective = buildEffectiveFilter(sidebarFilter)
loadPage(initialPage, effective, resolveSorter(nextSorter)) loadPage(initialPage, effective, resolveSorter(nextSorter), {
silent: true
})
}, },
[ [
buildEffectiveFilter, buildEffectiveFilter,
@ -1962,6 +2004,10 @@ const ObjectTable = forwardRef(
[isEditing, registerForm] [isEditing, registerForm]
) )
useEffect(() => {
console.log('loading', loading)
}, [loading])
const tableContent = ( const tableContent = (
<Flex <Flex
gap={'middle'} gap={'middle'}
@ -2006,7 +2052,12 @@ const ObjectTable = forwardRef(
<Spin <Spin
indicator={<LoadingOutlined />} indicator={<LoadingOutlined />}
spinning={loading} spinning={loading}
style={{ height: '100%', flex: 1, minHeight: 0 }} style={{
height: '300px',
flex: 1,
minHeight: 0,
width: '100%'
}}
> >
{renderCards()} {renderCards()}
</Spin> </Spin>
@ -2054,7 +2105,9 @@ const ObjectTable = forwardRef(
<Flex style={{ flex: 1, minHeight: 0 }}> <Flex style={{ flex: 1, minHeight: 0 }}>
<FilterSidebar <FilterSidebar
type={type} type={type}
filter={activeObjectView ? sidebarFilter : effectiveFilter} filter={
activeObjectView ? sidebarFilter : effectiveFilter
}
onFilterChange={handleSidebarFilterChange} onFilterChange={handleSidebarFilterChange}
masterFilter={resolvedMasterFilter} masterFilter={resolvedMasterFilter}
/> />