Compare commits
2 Commits
1466fe0f62
...
cb49baaf93
| Author | SHA1 | Date | |
|---|---|---|---|
| cb49baaf93 | |||
| dc237eecdd |
@ -475,6 +475,67 @@ body {
|
||||
.ant-spin-blur {
|
||||
filter: blur(3px);
|
||||
}
|
||||
|
||||
.spin {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-primary);
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.spin-nested {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.spin-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.spin-content.is-spinning {
|
||||
filter: blur(3px);
|
||||
opacity: 0.5;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spin-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-primary);
|
||||
font-size: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spin.spin-sm,
|
||||
.spin-nested.spin-sm .spin-overlay {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.spin.spin-lg,
|
||||
.spin-nested.spin-lg .spin-overlay {
|
||||
font-size: 32px;
|
||||
}
|
||||
/* --- End of src/index.css --- */
|
||||
|
||||
/* --- Start of src/components/Dashboard/Layout.css --- */
|
||||
@ -1377,26 +1438,6 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
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 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@ -1407,15 +1448,6 @@ 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;
|
||||
@ -1449,12 +1481,8 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.objectKanbanWrap > .ant-spin-nested-loading,
|
||||
.objectKanbanWrap > .ant-spin-nested-loading > .ant-spin-container {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.objectKanbanScroll {
|
||||
|
||||
@ -4,13 +4,29 @@ import PropTypes from 'prop-types'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const LoadingPlaceholder = ({ message, hasBackground = true }) => {
|
||||
const LoadingPlaceholder = ({
|
||||
message,
|
||||
hasBackground = true,
|
||||
hasBorder = true,
|
||||
height = undefined
|
||||
}) => {
|
||||
return (
|
||||
<Card
|
||||
size='small'
|
||||
style={{
|
||||
background: hasBackground == false ? 'transparent' : undefined,
|
||||
border: hasBackground == false ? '1px solid rgb(0 0 0 / 7%)' : undefined
|
||||
|
||||
border:
|
||||
hasBackground == false && hasBorder == false
|
||||
? '1px solid rgb(0 0 0 / 7%)'
|
||||
: hasBackground == true && hasBorder == false
|
||||
? 'none'
|
||||
: undefined
|
||||
}}
|
||||
styles={{
|
||||
body: {
|
||||
height: height == undefined ? undefined : height
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
@ -32,7 +48,9 @@ const LoadingPlaceholder = ({ message, hasBackground = true }) => {
|
||||
|
||||
LoadingPlaceholder.propTypes = {
|
||||
message: PropTypes.string.isRequired,
|
||||
hasBackground: PropTypes.bool
|
||||
hasBackground: PropTypes.bool,
|
||||
hasBorder: PropTypes.bool,
|
||||
height: PropTypes.string
|
||||
}
|
||||
|
||||
export default LoadingPlaceholder
|
||||
|
||||
@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
|
||||
import ObjectProperty from './ObjectProperty'
|
||||
import { createElement } from 'react'
|
||||
import Thumbnail from './Thumbnail'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
|
||||
const ObjectCard = ({
|
||||
isSkeleton = false,
|
||||
@ -13,6 +14,7 @@ const ObjectCard = ({
|
||||
isEditing = false,
|
||||
rowActions = [],
|
||||
renderActions,
|
||||
lazyLoading = false,
|
||||
cardStyle = 'borderless'
|
||||
}) => {
|
||||
const descriptionItems = []
|
||||
@ -59,7 +61,7 @@ const ObjectCard = ({
|
||||
var actions = undefined
|
||||
|
||||
if (rowActions.length > 0) {
|
||||
actions = renderActions(record)
|
||||
actions = renderActions(record, lazyLoading)
|
||||
}
|
||||
|
||||
const primaryDescriptionItems = thumbnailPresent
|
||||
@ -146,8 +148,20 @@ const ObjectCard = ({
|
||||
{actions && (
|
||||
<>
|
||||
<Divider style={{ margin: '4px 0 0 0' }} />
|
||||
<Flex align='flex-end' gap={10}>
|
||||
{actions}
|
||||
|
||||
<Flex
|
||||
align='center'
|
||||
gap={10}
|
||||
style={{ width: '100%' }}
|
||||
justify='space-between'
|
||||
>
|
||||
<Flex align='center' gap={10}>
|
||||
{actions}
|
||||
</Flex>
|
||||
|
||||
{lazyLoading && (
|
||||
<LoadingOutlined spin style={{ marginRight: 4 }} />
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
@ -166,7 +180,8 @@ ObjectCard.propTypes = {
|
||||
rowActions: PropTypes.array,
|
||||
renderActions: PropTypes.func.isRequired,
|
||||
cardStyle: PropTypes.string,
|
||||
isSkeleton: PropTypes.bool
|
||||
isSkeleton: PropTypes.bool,
|
||||
lazyLoading: PropTypes.bool
|
||||
}
|
||||
|
||||
export default ObjectCard
|
||||
|
||||
@ -8,14 +8,14 @@ import {
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { Empty, Flex, Spin } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { Empty, Flex } from 'antd'
|
||||
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 Spin from './Spin'
|
||||
import { getCategoryValueKey } from './viewModeUtils'
|
||||
|
||||
const KANBAN_COLUMN_WIDTH = 360
|
||||
@ -101,6 +101,7 @@ const ObjectKanban = forwardRef(
|
||||
baseFilter = {},
|
||||
masterFilter = {},
|
||||
sorter = {},
|
||||
lazyLoading = false,
|
||||
pageSize = 25,
|
||||
model,
|
||||
modelProperties,
|
||||
@ -311,11 +312,7 @@ const ObjectKanban = forwardRef(
|
||||
|
||||
return (
|
||||
<div className='objectKanbanContainerWrapper'>
|
||||
<Spin
|
||||
indicator={<LoadingOutlined />}
|
||||
spinning={showSkeleton}
|
||||
style={{ height: '100%', flex: 1, minHeight: 0 }}
|
||||
>
|
||||
<Spin spinning={showSkeleton}>
|
||||
<div className='objectKanbanContainerSkeletons'>
|
||||
<ObjectKanbanHeader
|
||||
categoryValues={categoryValues}
|
||||
@ -325,6 +322,7 @@ const ObjectKanban = forwardRef(
|
||||
skeletonColumnCount={
|
||||
showSkeleton ? skeletonLayout.columnCount : 0
|
||||
}
|
||||
lazyLoading={showSkeleton || lazyLoading}
|
||||
/>
|
||||
<div className='objectKanbanKanbanBody' ref={kanbanBodyRef}>
|
||||
{!showSkeleton && (
|
||||
@ -414,6 +412,7 @@ ObjectKanban.propTypes = {
|
||||
baseFilter: PropTypes.object,
|
||||
masterFilter: PropTypes.object,
|
||||
sorter: PropTypes.object,
|
||||
lazyLoading: PropTypes.bool,
|
||||
pageSize: PropTypes.number,
|
||||
model: PropTypes.object.isRequired,
|
||||
modelProperties: PropTypes.array.isRequired,
|
||||
|
||||
@ -9,11 +9,11 @@ import {
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { Flex, Spin } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { Flex } from 'antd'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
import ObjectCard from './ObjectCard'
|
||||
import Spin from './Spin'
|
||||
import { toCategoryFilterValue } from './viewModeUtils'
|
||||
|
||||
const SCROLL_THRESHOLD = 50
|
||||
@ -33,6 +33,7 @@ const ObjectKanbanColumn = forwardRef(
|
||||
visibleColumns = {},
|
||||
isEditing = false,
|
||||
rowActions = [],
|
||||
lazyLoading = false,
|
||||
renderActions,
|
||||
scrollElement = null
|
||||
},
|
||||
@ -358,60 +359,63 @@ const ObjectKanbanColumn = forwardRef(
|
||||
[loadBoundaryPage]
|
||||
)
|
||||
|
||||
const loadInitialPage = useCallback(async ({ silent = false } = {}) => {
|
||||
dataLoadGenerationRef.current += 1
|
||||
loadingPagesRef.current.clear()
|
||||
pendingScrollAnchorRef.current = null
|
||||
const generation = dataLoadGenerationRef.current
|
||||
const filter = {
|
||||
...masterFilterRef.current,
|
||||
...baseFilterRef.current,
|
||||
[categoryProperty]: toCategoryFilterValue(categoryValue)
|
||||
}
|
||||
const sorter = sorterRef.current
|
||||
|
||||
if (!silent) {
|
||||
pagesRef.current = []
|
||||
setPages([])
|
||||
setLoading(true)
|
||||
|
||||
const skeletonPage = createSkeletonPage(1)
|
||||
setTablePages([skeletonPage])
|
||||
} else {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
try {
|
||||
const firstResult = await fetchPage(1, { filter, sorter })
|
||||
if (isStaleDataLoad(generation)) return
|
||||
const loadedPages = [
|
||||
{
|
||||
pageNum: 1,
|
||||
items: firstResult.data || [],
|
||||
hasMore: firstResult.hasMore
|
||||
}
|
||||
]
|
||||
if (!isStaleDataLoad(generation)) {
|
||||
setTablePages(createPageWindow(loadedPages, 'next'))
|
||||
const loadInitialPage = useCallback(
|
||||
async ({ silent = false } = {}) => {
|
||||
dataLoadGenerationRef.current += 1
|
||||
loadingPagesRef.current.clear()
|
||||
pendingScrollAnchorRef.current = null
|
||||
const generation = dataLoadGenerationRef.current
|
||||
const filter = {
|
||||
...masterFilterRef.current,
|
||||
...baseFilterRef.current,
|
||||
[categoryProperty]: toCategoryFilterValue(categoryValue)
|
||||
}
|
||||
} catch {
|
||||
if (!silent && !isStaleDataLoad(generation)) {
|
||||
setTablePages([])
|
||||
}
|
||||
} finally {
|
||||
if (!silent && !isStaleDataLoad(generation)) {
|
||||
const sorter = sorterRef.current
|
||||
|
||||
if (!silent) {
|
||||
pagesRef.current = []
|
||||
setPages([])
|
||||
setLoading(true)
|
||||
|
||||
const skeletonPage = createSkeletonPage(1)
|
||||
setTablePages([skeletonPage])
|
||||
} else {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
categoryProperty,
|
||||
categoryValue,
|
||||
createPageWindow,
|
||||
createSkeletonPage,
|
||||
fetchPage,
|
||||
isStaleDataLoad,
|
||||
setTablePages
|
||||
])
|
||||
|
||||
try {
|
||||
const firstResult = await fetchPage(1, { filter, sorter })
|
||||
if (isStaleDataLoad(generation)) return
|
||||
const loadedPages = [
|
||||
{
|
||||
pageNum: 1,
|
||||
items: firstResult.data || [],
|
||||
hasMore: firstResult.hasMore
|
||||
}
|
||||
]
|
||||
if (!isStaleDataLoad(generation)) {
|
||||
setTablePages(createPageWindow(loadedPages, 'next'))
|
||||
}
|
||||
} catch {
|
||||
if (!silent && !isStaleDataLoad(generation)) {
|
||||
setTablePages([])
|
||||
}
|
||||
} finally {
|
||||
if (!silent && !isStaleDataLoad(generation)) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
categoryProperty,
|
||||
categoryValue,
|
||||
createPageWindow,
|
||||
createSkeletonPage,
|
||||
fetchPage,
|
||||
isStaleDataLoad,
|
||||
setTablePages
|
||||
]
|
||||
)
|
||||
|
||||
const reloadLoadedPages = useCallback(async () => {
|
||||
const loadedPages = pagesRef.current.filter(
|
||||
@ -501,7 +505,7 @@ const ObjectKanbanColumn = forwardRef(
|
||||
return (
|
||||
<Flex vertical className='objectKanbanColumn'>
|
||||
<div className='objectKanbanColumnCards' ref={containerRef}>
|
||||
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
||||
<Spin spinning={loading}>
|
||||
<Flex
|
||||
vertical
|
||||
gap='middle'
|
||||
@ -533,6 +537,7 @@ const ObjectKanbanColumn = forwardRef(
|
||||
rowActions={rowActions}
|
||||
renderActions={renderActions}
|
||||
cardStyle='bordered'
|
||||
lazyLoading={lazyLoading}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@ -560,6 +565,7 @@ ObjectKanbanColumn.propTypes = {
|
||||
visibleColumns: PropTypes.object,
|
||||
isEditing: PropTypes.bool,
|
||||
rowActions: PropTypes.array,
|
||||
lazyLoading: PropTypes.bool,
|
||||
renderActions: PropTypes.func,
|
||||
scrollElement: PropTypes.object
|
||||
}
|
||||
|
||||
@ -2,20 +2,22 @@ import { Flex, Skeleton } from 'antd'
|
||||
import PropTypes from 'prop-types'
|
||||
import ObjectProperty from './ObjectProperty'
|
||||
import { getCategoryValueKey } from './viewModeUtils'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
|
||||
const ObjectKanbanHeader = ({
|
||||
categoryValues,
|
||||
categoryProperty,
|
||||
categoryPropertyDef,
|
||||
trackRef,
|
||||
skeletonColumnCount = 0
|
||||
skeletonColumnCount = 0,
|
||||
lazyLoading = false
|
||||
}) => {
|
||||
const showSkeleton = skeletonColumnCount > 0
|
||||
|
||||
return (
|
||||
<div className='objectKanbanHeader'>
|
||||
<div className='objectKanbanHeaderTrack' ref={trackRef}>
|
||||
<Flex gap='middle' className='objectKanbanHeaderRow'>
|
||||
<Flex gap='middle' className='objectKanbanHeaderRow' align='center'>
|
||||
{showSkeleton
|
||||
? Array.from({ length: skeletonColumnCount }).map((_, index) => (
|
||||
<div
|
||||
@ -32,7 +34,13 @@ const ObjectKanbanHeader = ({
|
||||
: categoryValues.map((categoryValue) => {
|
||||
const columnKey = getCategoryValueKey(categoryValue)
|
||||
return (
|
||||
<div key={columnKey} className='objectKanbanHeaderCell'>
|
||||
<Flex
|
||||
key={columnKey}
|
||||
className='objectKanbanHeaderCell'
|
||||
align='center'
|
||||
justify='space-between'
|
||||
gap={8}
|
||||
>
|
||||
{categoryPropertyDef ? (
|
||||
<ObjectProperty
|
||||
{...categoryPropertyDef}
|
||||
@ -41,7 +49,13 @@ const ObjectKanbanHeader = ({
|
||||
name={categoryProperty}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{lazyLoading && (
|
||||
<LoadingOutlined
|
||||
spin
|
||||
style={{ flexShrink: 0, marginRight: 6 }}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
@ -57,7 +71,8 @@ ObjectKanbanHeader.propTypes = {
|
||||
categoryProperty: PropTypes.string.isRequired,
|
||||
categoryPropertyDef: PropTypes.object,
|
||||
trackRef: PropTypes.object,
|
||||
skeletonColumnCount: PropTypes.number
|
||||
skeletonColumnCount: PropTypes.number,
|
||||
lazyLoading: PropTypes.bool
|
||||
}
|
||||
|
||||
export default ObjectKanbanHeader
|
||||
|
||||
@ -15,7 +15,6 @@ import {
|
||||
Row,
|
||||
Col,
|
||||
Flex,
|
||||
Spin,
|
||||
Button,
|
||||
Space,
|
||||
Form,
|
||||
@ -47,6 +46,7 @@ import { useActions } from '../context/ActionsContext'
|
||||
import ActionsIcon from '../../Icons/ActionsIcon'
|
||||
import FilterIcon from '../../Icons/FilterIcon'
|
||||
import ScrollBox from './ScrollBox'
|
||||
import Spin from './Spin'
|
||||
import SimplePropertyFilter from './SimplePropertyFilter'
|
||||
import QuickPropertyFilters from './QuickPropertyFilters'
|
||||
import FilterInput from './FilterInput'
|
||||
@ -60,6 +60,7 @@ import Tooltip from './Tooltip'
|
||||
import { ObjectTableFilterContext } from './ObjectTableFilterContext'
|
||||
import ObjectListViewContext from '../context/ObjectListViewContext'
|
||||
import { isCardsView, isKanbanView, normalizeViewMode } from './viewModeUtils'
|
||||
import LoadingPlaceholder from './LoadingPlaceholder'
|
||||
|
||||
const logger = loglevel.getLogger('DasboardTable')
|
||||
logger.setLevel(config.logLevel)
|
||||
@ -563,7 +564,7 @@ const ObjectTable = forwardRef(
|
||||
)
|
||||
}, [])
|
||||
|
||||
const renderActions = (objectData) => {
|
||||
const renderActions = (objectData, actionsDisabled = false) => {
|
||||
return (
|
||||
<Flex gap='small' align='center' justify='center'>
|
||||
{rowActions.map((action, index) => {
|
||||
@ -576,9 +577,10 @@ const ObjectTable = forwardRef(
|
||||
action.disabled({
|
||||
...objectData,
|
||||
_user: userProfile
|
||||
})
|
||||
}) ||
|
||||
actionsDisabled
|
||||
} else {
|
||||
disabled = denied || action.disabled
|
||||
disabled = denied || action.disabled || actionsDisabled
|
||||
}
|
||||
}
|
||||
return (
|
||||
@ -1175,8 +1177,10 @@ const ObjectTable = forwardRef(
|
||||
|
||||
if (!silent) {
|
||||
setLoading(true)
|
||||
setLazyLoading(false)
|
||||
} else {
|
||||
setLoading(false)
|
||||
setLazyLoading(true)
|
||||
}
|
||||
try {
|
||||
let loadKanban = kanbanRef.current?.load
|
||||
@ -1191,8 +1195,12 @@ const ObjectTable = forwardRef(
|
||||
} catch (error) {
|
||||
logger.error('Error loading kanban view:', error)
|
||||
} finally {
|
||||
if (!silent && generation === dataLoadGenerationRef.current) {
|
||||
setLoading(false)
|
||||
if (generation === dataLoadGenerationRef.current) {
|
||||
if (!silent) {
|
||||
setLoading(false)
|
||||
} else {
|
||||
setLazyLoading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
@ -1204,6 +1212,7 @@ const ObjectTable = forwardRef(
|
||||
loadingPagesRef.current.clear()
|
||||
pendingScrollAnchorRef.current = null
|
||||
setLoading(false)
|
||||
setLazyLoading(true)
|
||||
|
||||
try {
|
||||
const firstResult = await fetchPage(pageNum, filter, sorter)
|
||||
@ -1224,6 +1233,10 @@ const ObjectTable = forwardRef(
|
||||
if (!isStaleDataLoad(generation)) {
|
||||
logger.error(`Error loading page ${pageNum}:`, error)
|
||||
}
|
||||
} finally {
|
||||
if (!isStaleDataLoad(generation)) {
|
||||
setLazyLoading(false)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -1235,6 +1248,7 @@ const ObjectTable = forwardRef(
|
||||
const skeletonPage = createSkeletonPage(pageNum)
|
||||
setTablePages([skeletonPage])
|
||||
setLoading(true)
|
||||
setLazyLoading(false)
|
||||
if (isStaleDataLoad(generation)) return
|
||||
|
||||
try {
|
||||
@ -1970,6 +1984,7 @@ const ObjectTable = forwardRef(
|
||||
modelProperties={modelProperties}
|
||||
visibleColumns={visibleColumns}
|
||||
record={record}
|
||||
lazyLoading={lazyLoading}
|
||||
isEditing={isEditing}
|
||||
rowActions={rowActions}
|
||||
renderActions={renderActions}
|
||||
@ -1981,6 +1996,14 @@ const ObjectTable = forwardRef(
|
||||
)
|
||||
})}
|
||||
</Row>
|
||||
{lazyLoading && tableData.length <= 0 && (
|
||||
<LoadingPlaceholder
|
||||
message='Loading, please wait...'
|
||||
hasBackground={true}
|
||||
hasBorder={false}
|
||||
height='260px'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ScrollBox>
|
||||
)
|
||||
@ -2022,11 +2045,7 @@ const ObjectTable = forwardRef(
|
||||
<Splitter.Panel>
|
||||
{isKanban ? (
|
||||
<div className='objectKanbanWrap'>
|
||||
<Spin
|
||||
indicator={<LoadingOutlined />}
|
||||
spinning={loading}
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<ObjectKanban
|
||||
ref={kanbanRef}
|
||||
type={type}
|
||||
@ -2034,6 +2053,7 @@ const ObjectTable = forwardRef(
|
||||
baseFilter={effectiveFilter}
|
||||
masterFilter={resolvedMasterFilter}
|
||||
sorter={effectiveSorter}
|
||||
lazyLoading={lazyLoading}
|
||||
pageSize={pageSize}
|
||||
model={model}
|
||||
modelProperties={modelProperties}
|
||||
@ -2049,41 +2069,28 @@ const ObjectTable = forwardRef(
|
||||
className='objectTableCardsContainer'
|
||||
ref={objectTableCardsContainerRef}
|
||||
>
|
||||
<Spin
|
||||
indicator={<LoadingOutlined />}
|
||||
spinning={loading}
|
||||
style={{
|
||||
height: '300px',
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{renderCards()}
|
||||
</Spin>
|
||||
<Spin spinning={loading}>{renderCards()}</Spin>
|
||||
</div>
|
||||
) : (
|
||||
<Table
|
||||
key={tableListKey}
|
||||
ref={tableRef}
|
||||
dataSource={tableData}
|
||||
columns={columnsWithSkeleton}
|
||||
className='dashboard-table'
|
||||
pagination={false}
|
||||
scroll={{ y: adjustedScrollHeight }}
|
||||
rowKey='_id'
|
||||
loading={{
|
||||
spinning: loading,
|
||||
indicator: <LoadingOutlined spin />
|
||||
}}
|
||||
onScroll={handleScroll}
|
||||
onChange={handleTableChange}
|
||||
showSorterTooltip={false}
|
||||
style={{ height: '100%' }}
|
||||
size={size}
|
||||
components={components}
|
||||
onRow={onRow}
|
||||
/>
|
||||
<Spin spinning={loading}>
|
||||
<Table
|
||||
key={tableListKey}
|
||||
ref={tableRef}
|
||||
dataSource={tableData}
|
||||
columns={columnsWithSkeleton}
|
||||
className='dashboard-table'
|
||||
pagination={false}
|
||||
scroll={{ y: adjustedScrollHeight }}
|
||||
rowKey='_id'
|
||||
onScroll={handleScroll}
|
||||
onChange={handleTableChange}
|
||||
showSorterTooltip={false}
|
||||
style={{ height: '100%' }}
|
||||
size={size}
|
||||
components={components}
|
||||
onRow={onRow}
|
||||
/>
|
||||
</Spin>
|
||||
)}
|
||||
</Splitter.Panel>
|
||||
{(showSortSidebar || showFilterSidebar) && (
|
||||
|
||||
77
src/components/Dashboard/common/Spin.jsx
Normal file
77
src/components/Dashboard/common/Spin.jsx
Normal file
@ -0,0 +1,77 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
|
||||
const SIZE_CLASS = {
|
||||
small: 'spin-sm',
|
||||
default: '',
|
||||
large: 'spin-lg'
|
||||
}
|
||||
|
||||
const DEFAULT_INDICATOR = (
|
||||
<LoadingOutlined spin style={{ color: 'var(--color-primary)' }} />
|
||||
)
|
||||
|
||||
const Spin = ({
|
||||
spinning = true,
|
||||
children,
|
||||
indicator = DEFAULT_INDICATOR,
|
||||
size = 'default',
|
||||
style,
|
||||
className
|
||||
}) => {
|
||||
const sizeClass = SIZE_CLASS[size] ?? ''
|
||||
|
||||
if (children == null) {
|
||||
if (!spinning) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['spin', sizeClass, className].filter(Boolean).join(' ')}
|
||||
style={style}
|
||||
role='status'
|
||||
aria-live='polite'
|
||||
>
|
||||
{indicator}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
'spin-nested',
|
||||
spinning ? 'is-spinning' : null,
|
||||
sizeClass,
|
||||
className
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{spinning && (
|
||||
<div className='spin-overlay' role='status' aria-live='polite'>
|
||||
{indicator}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={['spin-content', spinning ? 'is-spinning' : null]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-busy={spinning}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Spin.propTypes = {
|
||||
spinning: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
indicator: PropTypes.node,
|
||||
size: PropTypes.oneOf(['small', 'default', 'large']),
|
||||
style: PropTypes.object,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
export default Spin
|
||||
Loading…
x
Reference in New Issue
Block a user