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.
This commit is contained in:
parent
0eae922a9e
commit
26d8c80d54
@ -1372,16 +1372,29 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 0;
|
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 {
|
.objectTableCardsContainer .ant-spin-nested-loading {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.objectTableCardsContainer .ant-spin-container {
|
.objectTableCardsContainer .ant-spin-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.objectKanbanContainerWrapper {
|
.objectKanbanContainerWrapper {
|
||||||
@ -1394,6 +1407,15 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
flex-direction: column;
|
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 {
|
.objectKanbanContainerSkeletons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -8,14 +8,91 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react'
|
} 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 PropTypes from 'prop-types'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
|
import ObjectCard from './ObjectCard'
|
||||||
import ObjectKanbanColumn from './ObjectKanbanColumn'
|
import ObjectKanbanColumn from './ObjectKanbanColumn'
|
||||||
import ObjectKanbanHeader from './ObjectKanbanHeader'
|
import ObjectKanbanHeader from './ObjectKanbanHeader'
|
||||||
import ScrollBox from './ScrollBox'
|
import ScrollBox from './ScrollBox'
|
||||||
import { getCategoryValueKey } from './viewModeUtils'
|
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'
|
||||||
|
}) => (
|
||||||
|
<div className='objectKanbanSkeletonColumns objectKanbanSkeletonColumns--loading'>
|
||||||
|
<div className='objectKanbanSkeletonTrack' ref={trackRef}>
|
||||||
|
<Flex gap='middle' className='objectKanbanSkeletonRow'>
|
||||||
|
{Array.from({ length: columnCount }).map((_, columnIndex) => (
|
||||||
|
<div
|
||||||
|
key={`${keyPrefix}-${columnIndex}`}
|
||||||
|
className='objectKanbanColumnSkeleton'
|
||||||
|
>
|
||||||
|
<div className='objectKanbanColumnCards'>
|
||||||
|
{cardCount > 0 && (
|
||||||
|
<Flex
|
||||||
|
vertical
|
||||||
|
gap='middle'
|
||||||
|
className='objectKanbanColumnCardsInner'
|
||||||
|
>
|
||||||
|
{Array.from({ length: cardCount }).map((_, cardIndex) => (
|
||||||
|
<ObjectCard
|
||||||
|
key={cardIndex}
|
||||||
|
isSkeleton
|
||||||
|
model={model}
|
||||||
|
modelProperties={modelProperties}
|
||||||
|
visibleColumns={visibleColumns}
|
||||||
|
record={{
|
||||||
|
_id: `${keyPrefix}-${columnIndex}-${cardIndex}`
|
||||||
|
}}
|
||||||
|
cardStyle='bordered'
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
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(
|
const ObjectKanban = forwardRef(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -38,15 +115,24 @@ const ObjectKanban = forwardRef(
|
|||||||
const columnRefs = useRef({})
|
const columnRefs = useRef({})
|
||||||
const headerTrackRef = useRef(null)
|
const headerTrackRef = useRef(null)
|
||||||
const skeletonTrackRef = useRef(null)
|
const skeletonTrackRef = useRef(null)
|
||||||
|
const kanbanBodyRef = useRef(null)
|
||||||
const [scrollElement, setScrollElement] = useState(null)
|
const [scrollElement, setScrollElement] = useState(null)
|
||||||
const [categoryValues, setCategoryValues] = useState([])
|
const [categoryValues, setCategoryValues] = useState([])
|
||||||
const [hasLoaded, setHasLoaded] = useState(false)
|
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 baseFilterRef = useRef(baseFilter)
|
||||||
const masterFilterRef = useRef(masterFilter)
|
const masterFilterRef = useRef(masterFilter)
|
||||||
const sorterRef = useRef(sorter)
|
const sorterRef = useRef(sorter)
|
||||||
const getModelPropertyValuesRef = useRef(getModelPropertyValues)
|
const getModelPropertyValuesRef = useRef(getModelPropertyValues)
|
||||||
const lastCategoryQueryKeyRef = useRef(null)
|
const lastCategoryQueryKeyRef = useRef(null)
|
||||||
|
const hasLoadedRef = useRef(false)
|
||||||
|
const loadGenerationRef = useRef(0)
|
||||||
|
|
||||||
baseFilterRef.current = baseFilter
|
baseFilterRef.current = baseFilter
|
||||||
masterFilterRef.current = masterFilter
|
masterFilterRef.current = masterFilter
|
||||||
@ -57,6 +143,23 @@ const ObjectKanban = forwardRef(
|
|||||||
setScrollElement(node)
|
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(() => {
|
useLayoutEffect(() => {
|
||||||
const mainScroll = scrollElement
|
const mainScroll = scrollElement
|
||||||
const headerTrack = headerTrackRef.current
|
const headerTrack = headerTrackRef.current
|
||||||
@ -109,8 +212,11 @@ const ObjectKanban = forwardRef(
|
|||||||
|
|
||||||
const load = useCallback(
|
const load = useCallback(
|
||||||
async (filter = null, sorterArg = null) => {
|
async (filter = null, sorterArg = null) => {
|
||||||
|
const generation = ++loadGenerationRef.current
|
||||||
|
|
||||||
if (!categoryProperty) {
|
if (!categoryProperty) {
|
||||||
lastCategoryQueryKeyRef.current = null
|
lastCategoryQueryKeyRef.current = null
|
||||||
|
hasLoadedRef.current = true
|
||||||
setCategoryValues([])
|
setCategoryValues([])
|
||||||
setHasLoaded(true)
|
setHasLoaded(true)
|
||||||
return
|
return
|
||||||
@ -118,14 +224,21 @@ const ObjectKanban = forwardRef(
|
|||||||
|
|
||||||
const activeFilter = filter ?? baseFilterRef.current
|
const activeFilter = filter ?? baseFilterRef.current
|
||||||
const activeSorter = sorterArg ?? sorterRef.current
|
const activeSorter = sorterArg ?? sorterRef.current
|
||||||
|
const activeMasterFilter = masterFilterRef.current
|
||||||
const queryKey = buildCategoryQueryKey(activeFilter, activeSorter)
|
const queryKey = buildCategoryQueryKey(activeFilter, activeSorter)
|
||||||
|
|
||||||
if (lastCategoryQueryKeyRef.current === queryKey) {
|
const canReuseColumns =
|
||||||
|
lastCategoryQueryKeyRef.current === queryKey && hasLoadedRef.current
|
||||||
|
|
||||||
|
if (canReuseColumns) {
|
||||||
await reloadColumns()
|
await reloadColumns()
|
||||||
|
if (generation !== loadGenerationRef.current) return
|
||||||
|
hasLoadedRef.current = true
|
||||||
setHasLoaded(true)
|
setHasLoaded(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasLoadedRef.current = false
|
||||||
setHasLoaded(false)
|
setHasLoaded(false)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -134,19 +247,25 @@ const ObjectKanban = forwardRef(
|
|||||||
categoryProperty,
|
categoryProperty,
|
||||||
{
|
{
|
||||||
filter: activeFilter,
|
filter: activeFilter,
|
||||||
masterFilter: masterFilterRef.current
|
masterFilter: activeMasterFilter
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if (generation !== loadGenerationRef.current) return
|
||||||
const nextValues = Array.isArray(values) ? values : []
|
const nextValues = Array.isArray(values) ? values : []
|
||||||
setCategoryValues(nextValues)
|
|
||||||
lastCategoryQueryKeyRef.current = queryKey
|
lastCategoryQueryKeyRef.current = queryKey
|
||||||
} catch (error) {
|
hasLoadedRef.current = true
|
||||||
console.error('Error fetching kanban category values:', error)
|
setLoadedFilter(activeFilter)
|
||||||
setCategoryValues([])
|
setLoadedSorter(activeSorter)
|
||||||
lastCategoryQueryKeyRef.current = null
|
setCategoryValues(nextValues)
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
setHasLoaded(true)
|
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]
|
[buildCategoryQueryKey, categoryProperty, reloadColumns, type]
|
||||||
@ -154,6 +273,7 @@ const ObjectKanban = forwardRef(
|
|||||||
|
|
||||||
const reload = useCallback(async () => {
|
const reload = useCallback(async () => {
|
||||||
lastCategoryQueryKeyRef.current = null
|
lastCategoryQueryKeyRef.current = null
|
||||||
|
hasLoadedRef.current = false
|
||||||
await load(baseFilterRef.current, sorterRef.current)
|
await load(baseFilterRef.current, sorterRef.current)
|
||||||
}, [load])
|
}, [load])
|
||||||
|
|
||||||
@ -182,82 +302,100 @@ const ObjectKanban = forwardRef(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasLoaded || categoryValues.length === 0) {
|
const showSkeleton = !hasLoaded
|
||||||
return (
|
|
||||||
<div className='objectKanbanContainer' style={{ height: '100%' }} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='objectKanbanContainerWrapper'>
|
<div className='objectKanbanContainerWrapper'>
|
||||||
<div className='objectKanbanContainerSkeletons'>
|
<Spin
|
||||||
<ObjectKanbanHeader
|
indicator={<LoadingOutlined />}
|
||||||
categoryValues={categoryValues}
|
spinning={showSkeleton}
|
||||||
categoryProperty={categoryProperty}
|
style={{ height: '100%', flex: 1, minHeight: 0 }}
|
||||||
categoryPropertyDef={categoryPropertyDef}
|
>
|
||||||
trackRef={headerTrackRef}
|
<div className='objectKanbanContainerSkeletons'>
|
||||||
/>
|
<ObjectKanbanHeader
|
||||||
<div className='objectKanbanKanbanBody'>
|
categoryValues={categoryValues}
|
||||||
<div className='objectKanbanContainer'>
|
categoryProperty={categoryProperty}
|
||||||
<ScrollBox
|
categoryPropertyDef={categoryPropertyDef}
|
||||||
className='objectKanbanScroll'
|
trackRef={headerTrackRef}
|
||||||
scrollableNodeProps={{ ref: setScrollContainerRef }}
|
skeletonColumnCount={
|
||||||
>
|
showSkeleton ? skeletonLayout.columnCount : 0
|
||||||
<Flex gap='middle' className='objectKanban'>
|
}
|
||||||
{categoryValues.map((categoryValue) => {
|
/>
|
||||||
const columnKey = getCategoryValueKey(categoryValue)
|
<div className='objectKanbanKanbanBody' ref={kanbanBodyRef}>
|
||||||
return (
|
{!showSkeleton && (
|
||||||
<ObjectKanbanColumn
|
<div className='objectKanbanContainer'>
|
||||||
key={columnKey}
|
<ScrollBox
|
||||||
ref={(node) => {
|
className='objectKanbanScroll'
|
||||||
if (node) {
|
scrollableNodeProps={{ ref: setScrollContainerRef }}
|
||||||
columnRefs.current[columnKey] = node
|
>
|
||||||
} else {
|
<Flex gap='middle' className='objectKanban'>
|
||||||
delete columnRefs.current[columnKey]
|
{categoryValues.map((categoryValue) => {
|
||||||
}
|
const columnKey = getCategoryValueKey(categoryValue)
|
||||||
}}
|
return (
|
||||||
scrollElement={scrollElement}
|
<ObjectKanbanColumn
|
||||||
type={type}
|
key={columnKey}
|
||||||
categoryProperty={categoryProperty}
|
ref={(node) => {
|
||||||
categoryValue={categoryValue}
|
if (node) {
|
||||||
baseFilter={baseFilter}
|
columnRefs.current[columnKey] = node
|
||||||
masterFilter={masterFilter}
|
} else {
|
||||||
sorter={sorter}
|
delete columnRefs.current[columnKey]
|
||||||
pageSize={pageSize}
|
}
|
||||||
model={model}
|
}}
|
||||||
modelProperties={modelProperties}
|
scrollElement={scrollElement}
|
||||||
visibleColumns={visibleColumns}
|
type={type}
|
||||||
isEditing={isEditing}
|
categoryProperty={categoryProperty}
|
||||||
rowActions={rowActions}
|
categoryValue={categoryValue}
|
||||||
renderActions={renderActions}
|
baseFilter={loadedFilter}
|
||||||
/>
|
masterFilter={masterFilter}
|
||||||
)
|
sorter={loadedSorter}
|
||||||
})}
|
pageSize={pageSize}
|
||||||
</Flex>
|
model={model}
|
||||||
</ScrollBox>
|
modelProperties={modelProperties}
|
||||||
</div>
|
visibleColumns={visibleColumns}
|
||||||
<div className='objectKanbanSkeletonColumns'>
|
isEditing={isEditing}
|
||||||
<div
|
rowActions={rowActions}
|
||||||
className='objectKanbanSkeletonTrack'
|
renderActions={renderActions}
|
||||||
ref={skeletonTrackRef}
|
/>
|
||||||
>
|
)
|
||||||
<Flex gap='middle' className='objectKanbanSkeletonRow'>
|
})}
|
||||||
{categoryValues.map((categoryValue) => {
|
</Flex>
|
||||||
const columnKey = getCategoryValueKey(categoryValue)
|
</ScrollBox>
|
||||||
return (
|
</div>
|
||||||
<div
|
)}
|
||||||
key={columnKey}
|
{showSkeleton ? (
|
||||||
className='objectKanbanColumnSkeleton'
|
<ObjectKanbanSkeletonColumns
|
||||||
>
|
columnCount={skeletonLayout.columnCount}
|
||||||
<div className='objectKanbanColumnCards' />
|
cardCount={skeletonLayout.cardCount}
|
||||||
</div>
|
trackRef={skeletonTrackRef}
|
||||||
)
|
model={model}
|
||||||
})}
|
modelProperties={modelProperties}
|
||||||
</Flex>
|
visibleColumns={visibleColumns}
|
||||||
</div>
|
/>
|
||||||
|
) : (
|
||||||
|
<div className='objectKanbanSkeletonColumns'>
|
||||||
|
<div
|
||||||
|
className='objectKanbanSkeletonTrack'
|
||||||
|
ref={skeletonTrackRef}
|
||||||
|
>
|
||||||
|
<Flex gap='middle' className='objectKanbanSkeletonRow'>
|
||||||
|
{categoryValues.map((categoryValue) => {
|
||||||
|
const columnKey = getCategoryValueKey(categoryValue)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={columnKey}
|
||||||
|
className='objectKanbanColumnSkeleton'
|
||||||
|
>
|
||||||
|
<div className='objectKanbanColumnCards' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -164,17 +164,18 @@ const ObjectKanbanColumn = forwardRef(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const fetchPage = useCallback(
|
const fetchPage = useCallback(
|
||||||
async (pageNum) => {
|
async (pageNum, overrides = {}) => {
|
||||||
const filter = {
|
const filter = overrides.filter ?? {
|
||||||
...masterFilterRef.current,
|
...masterFilterRef.current,
|
||||||
...baseFilterRef.current,
|
...baseFilterRef.current,
|
||||||
[categoryProperty]: toCategoryFilterValue(categoryValue)
|
[categoryProperty]: toCategoryFilterValue(categoryValue)
|
||||||
}
|
}
|
||||||
|
const sorter = overrides.sorter ?? sorterRef.current
|
||||||
return fetchObjectsRef.current(type, {
|
return fetchObjectsRef.current(type, {
|
||||||
page: pageNum,
|
page: pageNum,
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
filter,
|
filter,
|
||||||
sorter: sorterRef.current
|
sorter
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[categoryProperty, categoryValue, pageSize, type]
|
[categoryProperty, categoryValue, pageSize, type]
|
||||||
@ -362,6 +363,12 @@ const ObjectKanbanColumn = forwardRef(
|
|||||||
loadingPagesRef.current.clear()
|
loadingPagesRef.current.clear()
|
||||||
pendingScrollAnchorRef.current = null
|
pendingScrollAnchorRef.current = null
|
||||||
const generation = dataLoadGenerationRef.current
|
const generation = dataLoadGenerationRef.current
|
||||||
|
const filter = {
|
||||||
|
...masterFilterRef.current,
|
||||||
|
...baseFilterRef.current,
|
||||||
|
[categoryProperty]: toCategoryFilterValue(categoryValue)
|
||||||
|
}
|
||||||
|
const sorter = sorterRef.current
|
||||||
|
|
||||||
pagesRef.current = []
|
pagesRef.current = []
|
||||||
setPages([])
|
setPages([])
|
||||||
@ -371,7 +378,7 @@ const ObjectKanbanColumn = forwardRef(
|
|||||||
setTablePages([skeletonPage])
|
setTablePages([skeletonPage])
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const firstResult = await fetchPage(1)
|
const firstResult = await fetchPage(1, { filter, sorter })
|
||||||
if (isStaleDataLoad(generation)) return
|
if (isStaleDataLoad(generation)) return
|
||||||
const loadedPages = [
|
const loadedPages = [
|
||||||
{
|
{
|
||||||
@ -393,6 +400,8 @@ const ObjectKanbanColumn = forwardRef(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
categoryProperty,
|
||||||
|
categoryValue,
|
||||||
createPageWindow,
|
createPageWindow,
|
||||||
createSkeletonPage,
|
createSkeletonPage,
|
||||||
fetchPage,
|
fetchPage,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Flex } from 'antd'
|
import { Flex, Skeleton } from 'antd'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ObjectProperty from './ObjectProperty'
|
import ObjectProperty from './ObjectProperty'
|
||||||
import { getCategoryValueKey } from './viewModeUtils'
|
import { getCategoryValueKey } from './viewModeUtils'
|
||||||
@ -7,27 +7,43 @@ const ObjectKanbanHeader = ({
|
|||||||
categoryValues,
|
categoryValues,
|
||||||
categoryProperty,
|
categoryProperty,
|
||||||
categoryPropertyDef,
|
categoryPropertyDef,
|
||||||
trackRef
|
trackRef,
|
||||||
|
skeletonColumnCount = 0
|
||||||
}) => {
|
}) => {
|
||||||
|
const showSkeleton = skeletonColumnCount > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='objectKanbanHeader'>
|
<div className='objectKanbanHeader'>
|
||||||
<div className='objectKanbanHeaderTrack' ref={trackRef}>
|
<div className='objectKanbanHeaderTrack' ref={trackRef}>
|
||||||
<Flex gap='middle' className='objectKanbanHeaderRow'>
|
<Flex gap='middle' className='objectKanbanHeaderRow'>
|
||||||
{categoryValues.map((categoryValue) => {
|
{showSkeleton
|
||||||
const columnKey = getCategoryValueKey(categoryValue)
|
? Array.from({ length: skeletonColumnCount }).map((_, index) => (
|
||||||
return (
|
<div
|
||||||
<div key={columnKey} className='objectKanbanHeaderCell'>
|
key={`skeleton-header-${index}`}
|
||||||
{categoryPropertyDef ? (
|
className='objectKanbanHeaderCell'
|
||||||
<ObjectProperty
|
>
|
||||||
{...categoryPropertyDef}
|
<Skeleton.Input
|
||||||
value={categoryValue}
|
active
|
||||||
objectData={{ [categoryProperty]: categoryValue }}
|
size='small'
|
||||||
name={categoryProperty}
|
style={{ width: '60%', height: 24 }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
</div>
|
||||||
</div>
|
))
|
||||||
)
|
: categoryValues.map((categoryValue) => {
|
||||||
})}
|
const columnKey = getCategoryValueKey(categoryValue)
|
||||||
|
return (
|
||||||
|
<div key={columnKey} className='objectKanbanHeaderCell'>
|
||||||
|
{categoryPropertyDef ? (
|
||||||
|
<ObjectProperty
|
||||||
|
{...categoryPropertyDef}
|
||||||
|
value={categoryValue}
|
||||||
|
objectData={{ [categoryProperty]: categoryValue }}
|
||||||
|
name={categoryProperty}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -40,7 +56,8 @@ ObjectKanbanHeader.propTypes = {
|
|||||||
categoryValues: PropTypes.array.isRequired,
|
categoryValues: PropTypes.array.isRequired,
|
||||||
categoryProperty: PropTypes.string.isRequired,
|
categoryProperty: PropTypes.string.isRequired,
|
||||||
categoryPropertyDef: PropTypes.object,
|
categoryPropertyDef: PropTypes.object,
|
||||||
trackRef: PropTypes.object
|
trackRef: PropTypes.object,
|
||||||
|
skeletonColumnCount: PropTypes.number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectKanbanHeader
|
export default ObjectKanbanHeader
|
||||||
|
|||||||
@ -66,6 +66,7 @@ logger.setLevel(config.logLevel)
|
|||||||
|
|
||||||
const SCROLL_THRESHOLD = 50
|
const SCROLL_THRESHOLD = 50
|
||||||
const SKELETON_HEIGHT = 49.5
|
const SKELETON_HEIGHT = 49.5
|
||||||
|
const EMPTY_MASTER_FILTER = {}
|
||||||
|
|
||||||
const getCardColSpan = (containerWidth) => {
|
const getCardColSpan = (containerWidth) => {
|
||||||
if (containerWidth >= 2980) return 2
|
if (containerWidth >= 2980) return 2
|
||||||
@ -275,7 +276,7 @@ const ObjectTable = forwardRef(
|
|||||||
viewMode: viewModeProp,
|
viewMode: viewModeProp,
|
||||||
cards = false,
|
cards = false,
|
||||||
visibleColumns = {},
|
visibleColumns = {},
|
||||||
masterFilter = {},
|
masterFilter,
|
||||||
size = 'middle',
|
size = 'middle',
|
||||||
onStateChange,
|
onStateChange,
|
||||||
showFilterSidebar = false,
|
showFilterSidebar = false,
|
||||||
@ -303,10 +304,11 @@ const ObjectTable = forwardRef(
|
|||||||
const isCards = isCardsView(viewMode)
|
const isCards = isCardsView(viewMode)
|
||||||
const isKanban = isKanbanView(viewMode)
|
const isKanban = isKanbanView(viewMode)
|
||||||
const kanbanRef = useRef(null)
|
const kanbanRef = useRef(null)
|
||||||
const wasKanbanRef = useRef(false)
|
const prevViewModeKeyRef = useRef(null)
|
||||||
const { token, userProfile } = useContext(AuthContext)
|
const { token, userProfile } = useContext(AuthContext)
|
||||||
const { isElectron } = useContext(ElectronContext)
|
const { isElectron } = useContext(ElectronContext)
|
||||||
const { callAction } = useActions()
|
const { callAction } = useActions()
|
||||||
|
const resolvedMasterFilter = masterFilter ?? EMPTY_MASTER_FILTER
|
||||||
const listViewId = objectListView?.listViewId ?? null
|
const listViewId = objectListView?.listViewId ?? null
|
||||||
const listViewFilter = objectListView?.listViewFilter ?? null
|
const listViewFilter = objectListView?.listViewFilter ?? null
|
||||||
const listViewSort = objectListView?.listViewSort ?? null
|
const listViewSort = objectListView?.listViewSort ?? null
|
||||||
@ -378,6 +380,7 @@ const ObjectTable = forwardRef(
|
|||||||
const tableSorterRef = useRef({})
|
const tableSorterRef = useRef({})
|
||||||
const listViewIdRef = useRef(listViewId)
|
const listViewIdRef = useRef(listViewId)
|
||||||
const listViewFilterRef = useRef(listViewFilter)
|
const listViewFilterRef = useRef(listViewFilter)
|
||||||
|
const sidebarViewIdRef = useRef(listViewId)
|
||||||
const activeObjectViewRef = useRef(activeObjectView)
|
const activeObjectViewRef = useRef(activeObjectView)
|
||||||
activeObjectViewRef.current = activeObjectView
|
activeObjectViewRef.current = activeObjectView
|
||||||
const onObjectViewFilterSortChangeRef = useRef(onObjectViewFilterSortChange)
|
const onObjectViewFilterSortChangeRef = useRef(onObjectViewFilterSortChange)
|
||||||
@ -393,13 +396,19 @@ const ObjectTable = forwardRef(
|
|||||||
listViewFilterRef.current = listViewFilter || {}
|
listViewFilterRef.current = listViewFilter || {}
|
||||||
}, [listViewId, listViewFilter])
|
}, [listViewId, listViewFilter])
|
||||||
|
|
||||||
|
const assignSidebarToView = useCallback((viewId) => {
|
||||||
|
sidebarViewIdRef.current = viewId ?? null
|
||||||
|
}, [])
|
||||||
|
|
||||||
const lastAppliedViewKeyRef = useRef(null)
|
const lastAppliedViewKeyRef = useRef(null)
|
||||||
|
|
||||||
const buildEffectiveFilter = useCallback(
|
const buildEffectiveFilter = useCallback(
|
||||||
(userFilter, viewFilter = listViewFilterRef.current) => {
|
(userFilter, viewFilter = listViewFilterRef.current) => {
|
||||||
const active = getActiveFilterValues(userFilter)
|
const active = getActiveFilterValues(userFilter)
|
||||||
const viewId = listViewIdRef.current
|
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 {
|
return {
|
||||||
...getActiveFilterValues(viewFilter || {}),
|
...getActiveFilterValues(viewFilter || {}),
|
||||||
...active
|
...active
|
||||||
@ -455,11 +464,11 @@ const ObjectTable = forwardRef(
|
|||||||
const subscribeToObjectTypeUpdatesFnRef = useRef(
|
const subscribeToObjectTypeUpdatesFnRef = useRef(
|
||||||
subscribeToObjectTypeUpdates
|
subscribeToObjectTypeUpdates
|
||||||
)
|
)
|
||||||
const prevValuesRef = useRef({ type, masterFilter })
|
const prevValuesRef = useRef({ type, masterFilter: resolvedMasterFilter })
|
||||||
|
|
||||||
const getMasterFilter = useCallback(
|
const getMasterFilter = useCallback(
|
||||||
() => masterFilter || {},
|
() => resolvedMasterFilter || EMPTY_MASTER_FILTER,
|
||||||
[masterFilter]
|
[resolvedMasterFilter]
|
||||||
)
|
)
|
||||||
|
|
||||||
const resolveSorter = useCallback((userSorter) => {
|
const resolveSorter = useCallback((userSorter) => {
|
||||||
@ -1042,15 +1051,26 @@ const ObjectTable = forwardRef(
|
|||||||
// view definition + any user-entered sidebar filters.
|
// view definition + any user-entered sidebar filters.
|
||||||
const effectiveFilter = useMemo(() => {
|
const effectiveFilter = useMemo(() => {
|
||||||
const userFilter = getActiveFilterValues(sidebarFilter)
|
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 {
|
return {
|
||||||
...getActiveFilterValues(listViewFilter || {}),
|
...viewFilter,
|
||||||
...userFilter
|
...userFilter
|
||||||
}
|
}
|
||||||
}, [listViewId, listViewFilter, sidebarFilter])
|
}, [activeObjectView, listViewId, listViewFilter, sidebarFilter])
|
||||||
|
|
||||||
const effectiveSorter = useMemo(() => {
|
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 }
|
return { field: tableSorter.field, order: tableSorter.order }
|
||||||
}
|
}
|
||||||
if (listViewId && listViewSort?.field && listViewSort?.order) {
|
if (listViewId && listViewSort?.field && listViewSort?.order) {
|
||||||
@ -1060,16 +1080,16 @@ const ObjectTable = forwardRef(
|
|||||||
}, [listViewId, listViewSort, tableSorter])
|
}, [listViewId, listViewSort, tableSorter])
|
||||||
|
|
||||||
const subscriptionFilter = useMemo(
|
const subscriptionFilter = useMemo(
|
||||||
() => ({ ...masterFilter, ...effectiveFilter }),
|
() => ({ ...resolvedMasterFilter, ...effectiveFilter }),
|
||||||
[effectiveFilter, masterFilter]
|
[effectiveFilter, resolvedMasterFilter]
|
||||||
)
|
)
|
||||||
|
|
||||||
const tableFilterContextValue = useMemo(
|
const tableFilterContextValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
filter: effectiveFilter,
|
filter: effectiveFilter,
|
||||||
masterFilter
|
masterFilter: resolvedMasterFilter
|
||||||
}),
|
}),
|
||||||
[effectiveFilter, masterFilter]
|
[effectiveFilter, resolvedMasterFilter]
|
||||||
)
|
)
|
||||||
|
|
||||||
newEventHandlerRef.current = newEventHandler
|
newEventHandlerRef.current = newEventHandler
|
||||||
@ -1142,27 +1162,33 @@ const ObjectTable = forwardRef(
|
|||||||
const loadPage = useCallback(
|
const loadPage = useCallback(
|
||||||
async (pageNum, filter = null, sorter = null) => {
|
async (pageNum, filter = null, sorter = null) => {
|
||||||
if (isKanban) {
|
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)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const activeFilter =
|
let loadKanban = kanbanRef.current?.load
|
||||||
filter != null ? filter : activeFilterRef.current
|
if (!loadKanban) {
|
||||||
const resolvedSorter =
|
|
||||||
sorter != null
|
|
||||||
? resolveSorter(sorter)
|
|
||||||
: resolveSorter(tableSorterRef.current)
|
|
||||||
|
|
||||||
if (!kanbanRef.current?.load) {
|
|
||||||
await new Promise((resolve) => requestAnimationFrame(resolve))
|
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||||
|
if (generation !== dataLoadGenerationRef.current) return
|
||||||
|
loadKanban = kanbanRef.current?.load
|
||||||
}
|
}
|
||||||
|
|
||||||
await kanbanRef.current?.load?.(
|
if (!loadKanban) return
|
||||||
activeFilter,
|
await loadKanban(activeFilter, sorterForLoad)
|
||||||
resolvedSorter?.field ? resolvedSorter : {}
|
|
||||||
)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error loading kanban view:', error)
|
logger.error('Error loading kanban view:', error)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
if (generation === dataLoadGenerationRef.current) {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1229,6 +1255,7 @@ const ObjectTable = forwardRef(
|
|||||||
const resolvedSorter = resolveSorter(sorter)
|
const resolvedSorter = resolveSorter(sorter)
|
||||||
|
|
||||||
if (Object.keys(filter).length > 0) {
|
if (Object.keys(filter).length > 0) {
|
||||||
|
assignSidebarToView(listViewIdRef.current)
|
||||||
setSidebarFilter(filter)
|
setSidebarFilter(filter)
|
||||||
}
|
}
|
||||||
if (sorter?.field) {
|
if (sorter?.field) {
|
||||||
@ -1242,6 +1269,7 @@ const ObjectTable = forwardRef(
|
|||||||
resolvedSorter.field ? resolvedSorter : null
|
resolvedSorter.field ? resolvedSorter : null
|
||||||
)
|
)
|
||||||
}, [
|
}, [
|
||||||
|
assignSidebarToView,
|
||||||
getPersistedFilter,
|
getPersistedFilter,
|
||||||
getPersistedSorter,
|
getPersistedSorter,
|
||||||
getViewFromUrl,
|
getViewFromUrl,
|
||||||
@ -1249,6 +1277,8 @@ const ObjectTable = forwardRef(
|
|||||||
loadPage,
|
loadPage,
|
||||||
resolveSorter
|
resolveSorter
|
||||||
])
|
])
|
||||||
|
const loadInitialPageRef = useRef(loadInitialPage)
|
||||||
|
loadInitialPageRef.current = loadInitialPage
|
||||||
|
|
||||||
// When a view becomes available, apply its definition once. Skip empty
|
// When a view becomes available, apply its definition once. Skip empty
|
||||||
// prop-lag updates so we never clear an already-applied view filter.
|
// prop-lag updates so we never clear an already-applied view filter.
|
||||||
@ -1275,6 +1305,7 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
lastAppliedViewKeyRef.current = viewKey
|
lastAppliedViewKeyRef.current = viewKey
|
||||||
|
|
||||||
|
assignSidebarToView(listViewId)
|
||||||
setSidebarFilter(viewFilter)
|
setSidebarFilter(viewFilter)
|
||||||
setTableSorter(viewSort)
|
setTableSorter(viewSort)
|
||||||
tableSorterRef.current = viewSort
|
tableSorterRef.current = viewSort
|
||||||
@ -1307,6 +1338,7 @@ const ObjectTable = forwardRef(
|
|||||||
listViewIdRef.current = null
|
listViewIdRef.current = null
|
||||||
listViewFilterRef.current = {}
|
listViewFilterRef.current = {}
|
||||||
lastAppliedViewKeyRef.current = null
|
lastAppliedViewKeyRef.current = null
|
||||||
|
assignSidebarToView(null)
|
||||||
filter = getAllTabPersistedFilter()
|
filter = getAllTabPersistedFilter()
|
||||||
sorter = getAllTabPersistedSorter()
|
sorter = getAllTabPersistedSorter()
|
||||||
|
|
||||||
@ -1352,6 +1384,7 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
lastAppliedViewKeyRef.current = `${viewId || ''}:${JSON.stringify(activeFilter)}:${JSON.stringify(nextSorter)}`
|
lastAppliedViewKeyRef.current = `${viewId || ''}:${JSON.stringify(activeFilter)}:${JSON.stringify(nextSorter)}`
|
||||||
|
|
||||||
|
assignSidebarToView(viewId)
|
||||||
setSidebarFilter(filter)
|
setSidebarFilter(filter)
|
||||||
tableSorterRef.current = nextSorter
|
tableSorterRef.current = nextSorter
|
||||||
setTableSorter(nextSorter)
|
setTableSorter(nextSorter)
|
||||||
@ -1364,6 +1397,7 @@ const ObjectTable = forwardRef(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
assignSidebarToView,
|
||||||
getAllTabPersistedFilter,
|
getAllTabPersistedFilter,
|
||||||
getAllTabPersistedSorter,
|
getAllTabPersistedSorter,
|
||||||
getViewFromUrl,
|
getViewFromUrl,
|
||||||
@ -1396,7 +1430,7 @@ const ObjectTable = forwardRef(
|
|||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
await loadInitialPage()
|
await loadInitialPageRef.current()
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
}
|
}
|
||||||
@ -1407,35 +1441,34 @@ const ObjectTable = forwardRef(
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [token, loadInitialPage, initialized, connected])
|
}, [token, initialized, connected])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isKanban || connected !== true || token == null) {
|
const viewModeKey = `${viewMode.type}:${viewMode?.settings?.categoryProperty || ''}`
|
||||||
wasKanbanRef.current = isKanban
|
|
||||||
|
if (connected !== true || token == null || !initialized) {
|
||||||
|
prevViewModeKeyRef.current = viewModeKey
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoryProperty = viewMode?.settings?.categoryProperty
|
if (prevViewModeKeyRef.current === viewModeKey) return
|
||||||
if (!categoryProperty) {
|
prevViewModeKeyRef.current = viewModeKey
|
||||||
wasKanbanRef.current = isKanban
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wasKanbanRef.current) {
|
if (isKanban && !viewMode?.settings?.categoryProperty) return
|
||||||
loadPage(
|
|
||||||
initialPage,
|
|
||||||
activeFilterRef.current,
|
|
||||||
tableSorterRef.current?.field ? tableSorterRef.current : null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
wasKanbanRef.current = isKanban
|
loadPage(
|
||||||
|
initialPage,
|
||||||
|
activeFilterRef.current,
|
||||||
|
tableSorterRef.current?.field ? tableSorterRef.current : null
|
||||||
|
)
|
||||||
}, [
|
}, [
|
||||||
isKanban,
|
isKanban,
|
||||||
connected,
|
connected,
|
||||||
token,
|
token,
|
||||||
|
initialized,
|
||||||
loadPage,
|
loadPage,
|
||||||
initialPage,
|
initialPage,
|
||||||
|
viewMode.type,
|
||||||
viewMode?.settings?.categoryProperty
|
viewMode?.settings?.categoryProperty
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -1445,7 +1478,8 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
const hasChanged =
|
const hasChanged =
|
||||||
prevValues.type !== type ||
|
prevValues.type !== type ||
|
||||||
JSON.stringify(prevValues.masterFilter) !== JSON.stringify(masterFilter)
|
JSON.stringify(prevValues.masterFilter) !==
|
||||||
|
JSON.stringify(resolvedMasterFilter)
|
||||||
|
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
beginDataReload()
|
beginDataReload()
|
||||||
@ -1453,14 +1487,15 @@ const ObjectTable = forwardRef(
|
|||||||
setPages([])
|
setPages([])
|
||||||
activeFilterRef.current = {}
|
activeFilterRef.current = {}
|
||||||
tableSorterRef.current = {}
|
tableSorterRef.current = {}
|
||||||
|
assignSidebarToView(null)
|
||||||
setSidebarFilter({})
|
setSidebarFilter({})
|
||||||
setTableSorter({})
|
setTableSorter({})
|
||||||
setInitialized(false)
|
setInitialized(false)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setLazyLoading(false)
|
setLazyLoading(false)
|
||||||
prevValuesRef.current = { type, masterFilter }
|
prevValuesRef.current = { type, masterFilter: resolvedMasterFilter }
|
||||||
}
|
}
|
||||||
}, [type, masterFilter, beginDataReload])
|
}, [type, resolvedMasterFilter, beginDataReload])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerPageFilter(effectiveFilter)
|
registerPageFilter(effectiveFilter)
|
||||||
@ -1493,7 +1528,7 @@ const ObjectTable = forwardRef(
|
|||||||
propertyLabel={propertyLabel}
|
propertyLabel={propertyLabel}
|
||||||
modelType={type}
|
modelType={type}
|
||||||
filter={effectiveFilter}
|
filter={effectiveFilter}
|
||||||
masterFilter={masterFilter}
|
masterFilter={resolvedMasterFilter}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1517,25 +1552,27 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSidebarFilter(next)
|
setSidebarFilter(next)
|
||||||
|
assignSidebarToView(listViewIdRef.current)
|
||||||
setTableSorter(nextSorter)
|
setTableSorter(nextSorter)
|
||||||
const effective = buildEffectiveFilter(next)
|
|
||||||
activeFilterRef.current = effective
|
|
||||||
if (activeObjectViewRef.current) {
|
if (activeObjectViewRef.current) {
|
||||||
|
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) {
|
||||||
// All tab: persist personal filter/sort to URL/session
|
// All tab: persist personal filter/sort to URL/session
|
||||||
persistTableState(next, nextSorter)
|
persistTableState(next, nextSorter)
|
||||||
}
|
}
|
||||||
|
const effective = buildEffectiveFilter(next)
|
||||||
|
activeFilterRef.current = effective
|
||||||
loadPage(initialPage, effective, resolveSorter(nextSorter))
|
loadPage(initialPage, effective, resolveSorter(nextSorter))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSidebarFilterChange = useCallback(
|
const handleSidebarFilterChange = useCallback(
|
||||||
(newSidebarFilter) => {
|
(newSidebarFilter) => {
|
||||||
setSidebarFilter(newSidebarFilter)
|
setSidebarFilter(newSidebarFilter)
|
||||||
const effective = buildEffectiveFilter(newSidebarFilter)
|
assignSidebarToView(listViewIdRef.current)
|
||||||
activeFilterRef.current = effective
|
|
||||||
if (activeObjectViewRef.current) {
|
if (activeObjectViewRef.current) {
|
||||||
|
listViewFilterRef.current = newSidebarFilter
|
||||||
onObjectViewFilterSortChangeRef.current?.(
|
onObjectViewFilterSortChangeRef.current?.(
|
||||||
newSidebarFilter,
|
newSidebarFilter,
|
||||||
tableSorter
|
tableSorter
|
||||||
@ -1543,9 +1580,12 @@ const ObjectTable = forwardRef(
|
|||||||
} else if (!listViewIdRef.current) {
|
} else if (!listViewIdRef.current) {
|
||||||
persistFilter(newSidebarFilter)
|
persistFilter(newSidebarFilter)
|
||||||
}
|
}
|
||||||
|
const effective = buildEffectiveFilter(newSidebarFilter)
|
||||||
|
activeFilterRef.current = effective
|
||||||
loadPage(initialPage, effective, resolveSorter(tableSorter))
|
loadPage(initialPage, effective, resolveSorter(tableSorter))
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
assignSidebarToView,
|
||||||
buildEffectiveFilter,
|
buildEffectiveFilter,
|
||||||
initialPage,
|
initialPage,
|
||||||
loadPage,
|
loadPage,
|
||||||
@ -1568,11 +1608,8 @@ const ObjectTable = forwardRef(
|
|||||||
} else if (!listViewIdRef.current) {
|
} else if (!listViewIdRef.current) {
|
||||||
persistSort(nextSorter)
|
persistSort(nextSorter)
|
||||||
}
|
}
|
||||||
loadPage(
|
const effective = buildEffectiveFilter(sidebarFilter)
|
||||||
initialPage,
|
loadPage(initialPage, effective, resolveSorter(nextSorter))
|
||||||
buildEffectiveFilter(sidebarFilter),
|
|
||||||
resolveSorter(nextSorter)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
buildEffectiveFilter,
|
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 = ({
|
columnConfig.filterDropdown = ({
|
||||||
setSelectedKeys,
|
setSelectedKeys,
|
||||||
selectedKeys,
|
selectedKeys,
|
||||||
@ -1851,7 +1891,9 @@ const ObjectTable = forwardRef(
|
|||||||
return (
|
return (
|
||||||
<ScrollBox
|
<ScrollBox
|
||||||
style={{
|
style={{
|
||||||
maxHeight: `100%`
|
height: '100%',
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='objectTableCards'>
|
<div className='objectTableCards'>
|
||||||
@ -1944,7 +1986,7 @@ const ObjectTable = forwardRef(
|
|||||||
type={type}
|
type={type}
|
||||||
categoryProperty={viewMode.settings?.categoryProperty}
|
categoryProperty={viewMode.settings?.categoryProperty}
|
||||||
baseFilter={effectiveFilter}
|
baseFilter={effectiveFilter}
|
||||||
masterFilter={masterFilter}
|
masterFilter={resolvedMasterFilter}
|
||||||
sorter={effectiveSorter}
|
sorter={effectiveSorter}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
model={model}
|
model={model}
|
||||||
@ -1964,7 +2006,7 @@ const ObjectTable = forwardRef(
|
|||||||
<Spin
|
<Spin
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
spinning={loading}
|
spinning={loading}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%', flex: 1, minHeight: 0 }}
|
||||||
>
|
>
|
||||||
{renderCards()}
|
{renderCards()}
|
||||||
</Spin>
|
</Spin>
|
||||||
@ -2012,9 +2054,9 @@ const ObjectTable = forwardRef(
|
|||||||
<Flex style={{ flex: 1, minHeight: 0 }}>
|
<Flex style={{ flex: 1, minHeight: 0 }}>
|
||||||
<FilterSidebar
|
<FilterSidebar
|
||||||
type={type}
|
type={type}
|
||||||
filter={effectiveFilter}
|
filter={activeObjectView ? sidebarFilter : effectiveFilter}
|
||||||
onFilterChange={handleSidebarFilterChange}
|
onFilterChange={handleSidebarFilterChange}
|
||||||
masterFilter={masterFilter}
|
masterFilter={resolvedMasterFilter}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -119,7 +119,7 @@ const ObjectTableViewButton = ({
|
|||||||
).map((option) => (
|
).map((option) => (
|
||||||
<Radio key={option.type} value={option.type}>
|
<Radio key={option.type} value={option.type}>
|
||||||
<Flex
|
<Flex
|
||||||
gap='4px'
|
gap='8px'
|
||||||
align='center'
|
align='center'
|
||||||
style={{ marginLeft: '4px' }}
|
style={{ marginLeft: '4px' }}
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user