778 lines
23 KiB
JavaScript
778 lines
23 KiB
JavaScript
import {
|
|
forwardRef,
|
|
useImperativeHandle,
|
|
useRef,
|
|
useEffect,
|
|
useState,
|
|
useCallback,
|
|
createElement
|
|
} from 'react'
|
|
import {
|
|
Table,
|
|
message,
|
|
Skeleton,
|
|
Card,
|
|
Row,
|
|
Col,
|
|
Descriptions,
|
|
Flex,
|
|
Spin,
|
|
Button,
|
|
Input,
|
|
Space,
|
|
Tooltip
|
|
} from 'antd'
|
|
import { LoadingOutlined } from '@ant-design/icons'
|
|
import PropTypes from 'prop-types'
|
|
import { useMediaQuery } from 'react-responsive'
|
|
import { useContext } from 'react'
|
|
import { ApiServerContext } from '../context/ApiServerContext'
|
|
import config from '../../../config'
|
|
import loglevel from 'loglevel'
|
|
import {
|
|
getModelProperties,
|
|
getModelByName
|
|
} from '../../../database/ObjectModels'
|
|
import ObjectProperty from './ObjectProperty'
|
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
|
import CheckIcon from '../../Icons/CheckIcon'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
|
|
import { AuthContext } from '../context/AuthContext'
|
|
import unionBy from 'lodash/unionBy'
|
|
const logger = loglevel.getLogger('DasboardTable')
|
|
logger.setLevel(config.logLevel)
|
|
|
|
const ObjectTable = forwardRef(
|
|
(
|
|
{
|
|
type,
|
|
pageSize = 25,
|
|
scrollHeight = 'calc(var(--unit-100vh) - 270px)',
|
|
onDataChange,
|
|
initialPage = 1,
|
|
cards = false,
|
|
visibleColumns = {},
|
|
masterFilter = {}
|
|
},
|
|
ref
|
|
) => {
|
|
const { token } = useContext(AuthContext)
|
|
const {
|
|
fetchObjects,
|
|
connected,
|
|
subscribeToObjectUpdates,
|
|
subscribeToObjectTypeUpdates
|
|
} = useContext(ApiServerContext)
|
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
const navigate = useNavigate()
|
|
var adjustedScrollHeight = scrollHeight
|
|
if (isMobile) {
|
|
adjustedScrollHeight = 'calc(var(--unit-100vh) - 316px)'
|
|
}
|
|
if (cards) {
|
|
adjustedScrollHeight = 'calc(var(--unit-100vh) - 280px)'
|
|
}
|
|
const [, contextHolder] = message.useMessage()
|
|
const tableRef = useRef(null)
|
|
const model = getModelByName(type)
|
|
const [tableFilter, setTableFilter] = useState({})
|
|
const [tableSorter, setTableSorter] = useState({})
|
|
const [initialized, setInitialized] = useState(false)
|
|
|
|
// Table state
|
|
const [pages, setPages] = useState([])
|
|
const pagesRef = useRef(pages)
|
|
const [hasMore, setHasMore] = useState(true)
|
|
const [loading, setLoading] = useState(true)
|
|
const [lazyLoading, setLazyLoading] = useState(false)
|
|
|
|
const subscribedIdsRef = useRef([])
|
|
// const [typeSubscribed, setTypeSubscribed] = useState(false)
|
|
const unsubscribesRef = useRef([])
|
|
const updateEventHandlerRef = useRef()
|
|
const subscribeToObjectTypeUpdatesRef = useRef(null)
|
|
|
|
const rowActions =
|
|
model.actions?.filter((action) => action.row == true) || []
|
|
|
|
const createSkeletonData = useCallback(() => {
|
|
return Array(pageSize)
|
|
.fill(null)
|
|
.map(() => ({
|
|
_id: `skeleton-${Math.random().toString(36).substring(2, 15)}`,
|
|
isSkeleton: true
|
|
}))
|
|
}, [pageSize])
|
|
|
|
const renderActions = (objectData) => {
|
|
return (
|
|
<Flex gap='small' align='center' justify='center'>
|
|
{rowActions.map((action, index) => (
|
|
<Tooltip key={index} title={action.label} arrow={false}>
|
|
<Button
|
|
icon={
|
|
action.icon ? (
|
|
createElement(action.icon)
|
|
) : (
|
|
<QuestionCircleIcon />
|
|
)
|
|
}
|
|
type={'text'}
|
|
size={'small'}
|
|
onClick={() => {
|
|
if (action.url) {
|
|
navigate(action.url(objectData._id))
|
|
}
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
))}
|
|
</Flex>
|
|
)
|
|
}
|
|
|
|
const fetchData = useCallback(
|
|
async (pageNum = 1, filter = null, sorter = null) => {
|
|
if (filter == null) {
|
|
filter = tableFilter
|
|
} else {
|
|
setTableFilter(filter)
|
|
}
|
|
if (sorter == null) {
|
|
sorter = tableSorter
|
|
} else {
|
|
setTableSorter({
|
|
field: sorter.field,
|
|
order: sorter.order
|
|
})
|
|
}
|
|
console.log('Fetching data...')
|
|
try {
|
|
const result = await fetchObjects(type, {
|
|
page: pageNum,
|
|
limit: pageSize,
|
|
filter: { ...filter, ...masterFilter },
|
|
sorter,
|
|
onDataChange
|
|
})
|
|
|
|
setHasMore(result.hasMore)
|
|
|
|
setPages((prev) => {
|
|
const existingPageIndex = prev.findIndex(
|
|
(p) => p.pageNum === pageNum
|
|
)
|
|
logger.debug(prev.map((p) => p.pageNum))
|
|
if (existingPageIndex !== -1) {
|
|
// Update existing page
|
|
const newPages = [...prev]
|
|
newPages[existingPageIndex] = { pageNum, items: result.data }
|
|
return newPages
|
|
}
|
|
// If page doesn't exist, return unchanged
|
|
return prev
|
|
})
|
|
|
|
setLoading(false)
|
|
setLazyLoading(false)
|
|
return result.data || []
|
|
} catch (error) {
|
|
setPages((prev) =>
|
|
prev.map((page) => ({
|
|
...page,
|
|
items: page.items.filter((item) => !item.isSkeleton)
|
|
}))
|
|
)
|
|
setLoading(false)
|
|
setLazyLoading(false)
|
|
throw error
|
|
}
|
|
},
|
|
[
|
|
type,
|
|
masterFilter,
|
|
pageSize,
|
|
tableFilter,
|
|
tableSorter,
|
|
onDataChange,
|
|
fetchObjects
|
|
]
|
|
)
|
|
|
|
const loadNextPage = useCallback(() => {
|
|
const highestPage = Math.max(...pages.map((p) => p.pageNum))
|
|
const nextPage = highestPage + 1
|
|
if (hasMore) {
|
|
setPages((prev) => {
|
|
const filteredPages = prev.map((page) => ({
|
|
...page,
|
|
items: page.items.filter((item) => !item.isSkeleton)
|
|
}))
|
|
const minPage = Math.min(...filteredPages.map((p) => p.pageNum))
|
|
const relevantPages = filteredPages.filter(
|
|
(p) => p.pageNum !== minPage
|
|
)
|
|
return [
|
|
...relevantPages,
|
|
{ pageNum: nextPage, items: createSkeletonData() }
|
|
]
|
|
})
|
|
fetchData(nextPage)
|
|
}
|
|
}, [pages, createSkeletonData, fetchData, hasMore])
|
|
|
|
const loadPreviousPage = useCallback(() => {
|
|
const lowestPage = Math.min(...pages.map((p) => p.pageNum))
|
|
const prevPage = lowestPage - 1
|
|
|
|
if (prevPage > 0) {
|
|
setPages((prev) => {
|
|
const filteredPages = prev.map((page) => ({
|
|
...page,
|
|
items: page.items.filter((item) => !item.isSkeleton)
|
|
}))
|
|
const maxPage = Math.max(...filteredPages.map((p) => p.pageNum))
|
|
const relevantPages = filteredPages.filter(
|
|
(p) => p.pageNum !== maxPage
|
|
)
|
|
return [
|
|
{ pageNum: prevPage, items: createSkeletonData() },
|
|
...relevantPages
|
|
]
|
|
})
|
|
fetchData(prevPage)
|
|
}
|
|
}, [pages, createSkeletonData, fetchData])
|
|
|
|
const handleScroll = useCallback(
|
|
(e) => {
|
|
const { target } = e
|
|
const scrollHeight = target.scrollHeight
|
|
const scrollTop = target.scrollTop
|
|
const clientHeight = target.clientHeight
|
|
const lowestPage = Math.min(...pages.map((p) => p.pageNum))
|
|
const prevPage = lowestPage - 1
|
|
|
|
// Load more data when scrolling down
|
|
if (scrollHeight - scrollTop - clientHeight < 100 && hasMore) {
|
|
setTimeout(() => {
|
|
target.scrollTop = scrollHeight / 2
|
|
}, 0)
|
|
setLazyLoading(true)
|
|
logger.debug('Loading next page...')
|
|
loadNextPage()
|
|
}
|
|
|
|
// Load previous data when scrolling up
|
|
if (scrollTop < 100 && prevPage > 0) {
|
|
setTimeout(() => {
|
|
target.scrollTop = scrollHeight / 2
|
|
}, 0)
|
|
setLazyLoading(true)
|
|
logger.debug('Loading previous page...')
|
|
loadPreviousPage()
|
|
}
|
|
},
|
|
[loadNextPage, loadPreviousPage, hasMore, pages]
|
|
)
|
|
|
|
const reload = useCallback(async () => {
|
|
setLazyLoading(true)
|
|
for (let i = 0; i < pagesRef.current.length; i++) {
|
|
const page = pagesRef.current[i]
|
|
await fetchData(page.pageNum)
|
|
}
|
|
}, [fetchData])
|
|
|
|
// Update event handler for real-time updates
|
|
const updateEventHandler = useCallback((id, updatedData) => {
|
|
setPages((prevPages) =>
|
|
prevPages.map((page) => {
|
|
const updatedItems = unionBy(
|
|
[{ ...updatedData, _id: id }],
|
|
page.items,
|
|
'_id'
|
|
)
|
|
return {
|
|
...page,
|
|
items: updatedItems
|
|
}
|
|
})
|
|
)
|
|
}, [])
|
|
|
|
// Store the latest updateEventHandler in a ref
|
|
updateEventHandlerRef.current = updateEventHandler
|
|
|
|
const newEventHandler = useCallback(() => {
|
|
reload()
|
|
}, [reload])
|
|
|
|
// Subscribe to real-time updates for all items
|
|
useEffect(() => {
|
|
if (pages.length > 0 && connected == true) {
|
|
// Get all non-skeleton item IDs from all pages
|
|
const allItemIds = pages
|
|
.flatMap((page) => page.items || [])
|
|
.filter((item) => !item.isSkeleton)
|
|
.map((item) => item._id)
|
|
.filter(Boolean)
|
|
|
|
// Find new items that need subscription
|
|
const newItemIds = allItemIds.filter(
|
|
(id) => !subscribedIdsRef.current.includes(id)
|
|
)
|
|
|
|
// Subscribe to new items only
|
|
newItemIds.forEach((itemId) => {
|
|
const unsubscribe = subscribeToObjectUpdates(
|
|
itemId,
|
|
type,
|
|
(updateData) => {
|
|
updateEventHandlerRef.current(itemId, updateData)
|
|
}
|
|
)
|
|
subscribedIdsRef.current.push(itemId)
|
|
if (unsubscribe) {
|
|
unsubscribesRef.current.push(unsubscribe)
|
|
}
|
|
})
|
|
|
|
// Clean up subscriptions for items that are no longer in the pages
|
|
const currentSubscribedIds = subscribedIdsRef.current
|
|
const itemsToUnsubscribe = currentSubscribedIds.filter(
|
|
(id) => !allItemIds.includes(id)
|
|
)
|
|
|
|
itemsToUnsubscribe.forEach((itemId) => {
|
|
const index = subscribedIdsRef.current.indexOf(itemId)
|
|
if (index > -1) {
|
|
subscribedIdsRef.current.splice(index, 1)
|
|
const unsubscribe = unsubscribesRef.current[index]
|
|
if (unsubscribe) {
|
|
unsubscribe()
|
|
}
|
|
unsubscribesRef.current.splice(index, 1)
|
|
}
|
|
})
|
|
}
|
|
}, [pages, type, subscribeToObjectUpdates, connected])
|
|
|
|
// Cleanup effect for component unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
if (connected == true && unsubscribesRef.current) {
|
|
// Clean up all subscriptions when component unmounts
|
|
unsubscribesRef.current.forEach((unsubscribe) => {
|
|
if (unsubscribe) unsubscribe()
|
|
})
|
|
unsubscribesRef.current = []
|
|
subscribedIdsRef.current = []
|
|
|
|
// Clean up type subscription
|
|
}
|
|
if (connected == true && subscribeToObjectTypeUpdatesRef.current) {
|
|
subscribeToObjectTypeUpdatesRef.current()
|
|
subscribeToObjectTypeUpdatesRef.current = null
|
|
}
|
|
}
|
|
}, [connected])
|
|
|
|
useEffect(() => {
|
|
if (
|
|
connected == true &&
|
|
subscribeToObjectTypeUpdatesRef.current == null
|
|
) {
|
|
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
|
|
type,
|
|
newEventHandler
|
|
)
|
|
}
|
|
}, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])
|
|
|
|
const updateData = useCallback(
|
|
(_id, updatedData) => {
|
|
updateEventHandler({ _id, ...updatedData })
|
|
},
|
|
[updateEventHandler]
|
|
)
|
|
|
|
const loadPage = useCallback(
|
|
async (pageNum, filter = null, sorter = null) => {
|
|
// Create initial page with skeletons
|
|
setPages([{ pageNum: pageNum, items: createSkeletonData() }])
|
|
|
|
const items = await fetchData(pageNum, filter, sorter)
|
|
|
|
if (items.length >= 25) {
|
|
setPages((prev) => {
|
|
// Remove any existing page with the same pageNum
|
|
const filtered = prev.filter((p) => p.pageNum !== pageNum + 1)
|
|
return [
|
|
...filtered,
|
|
{ pageNum: pageNum + 1, items: createSkeletonData() }
|
|
]
|
|
})
|
|
await fetchData(pageNum + 1, filter, sorter)
|
|
}
|
|
},
|
|
[createSkeletonData, fetchData]
|
|
)
|
|
|
|
const loadInitialPage = useCallback(async () => {
|
|
loadPage(initialPage)
|
|
}, [initialPage, loadPage])
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
reload,
|
|
setData: (newData) => {
|
|
setPages([{ pageNum: 1, items: newData }])
|
|
},
|
|
updateData
|
|
}))
|
|
|
|
useEffect(() => {
|
|
if (token != null && !pages.includes(initialPage) && !initialized) {
|
|
loadInitialPage()
|
|
setInitialized(true)
|
|
}
|
|
}, [token, loadInitialPage, initialPage, pages, initialized])
|
|
|
|
const getFilterDropdown = ({
|
|
setSelectedKeys,
|
|
selectedKeys,
|
|
confirm,
|
|
clearFilters,
|
|
propertyName
|
|
}) => {
|
|
return (
|
|
<div style={{ padding: 8 }}>
|
|
<Space.Compact>
|
|
<Input
|
|
placeholder={'Search ' + propertyName}
|
|
value={selectedKeys[0]}
|
|
onChange={(e) =>
|
|
setSelectedKeys(e.target.value ? [e.target.value] : [])
|
|
}
|
|
onPressEnter={() => confirm()}
|
|
style={{ width: 200, display: 'block' }}
|
|
/>
|
|
<Button
|
|
onClick={() => {
|
|
clearFilters()
|
|
confirm()
|
|
}}
|
|
icon={<XMarkIcon />}
|
|
/>
|
|
<Button
|
|
type='primary'
|
|
onClick={() => confirm()}
|
|
icon={<CheckIcon />}
|
|
/>
|
|
</Space.Compact>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const handleTableChange = (pagination, filters, sorter) => {
|
|
const newFilters = {}
|
|
|
|
Object.entries(filters).forEach(([key, value]) => {
|
|
if (value && value.length > 0) {
|
|
newFilters[key] = value[0]
|
|
}
|
|
})
|
|
|
|
setPages([])
|
|
setLoading(true)
|
|
loadPage(initialPage, newFilters, {
|
|
field: sorter.field,
|
|
order: sorter.order
|
|
})
|
|
}
|
|
|
|
const modelProperties = getModelProperties(type)
|
|
// Table columns from model properties
|
|
const columnsWithSkeleton = [
|
|
{
|
|
title: lazyLoading ? <LoadingOutlined /> : cards ? model.icon : null,
|
|
key: 'icon',
|
|
width: 45,
|
|
fixed: 'left',
|
|
render: () => createElement(model.icon)
|
|
}
|
|
]
|
|
|
|
useEffect(() => {
|
|
pagesRef.current = pages
|
|
}, [pages])
|
|
// Add columns in the order specified by model.columns
|
|
model.columns.forEach((colName) => {
|
|
const prop = modelProperties.find((p) => p.name === colName)
|
|
if (prop) {
|
|
// Check if column should be visible based on visibleColumns prop
|
|
if (
|
|
Object.keys(visibleColumns).length > 0 &&
|
|
visibleColumns[prop.name] === false
|
|
) {
|
|
return // Skip this column if it's not visible
|
|
}
|
|
|
|
var fixed = prop.columnFixed || undefined
|
|
var width = 200
|
|
|
|
switch (prop.type) {
|
|
case 'text':
|
|
width = 200
|
|
break
|
|
case 'number':
|
|
width = 100
|
|
break
|
|
case 'dateTime':
|
|
width = 200
|
|
break
|
|
case 'state':
|
|
width = 200
|
|
break
|
|
case 'id':
|
|
width = 180
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
// Check if this property should be filterable based on model.filters
|
|
const isFilterable = model.filters && model.filters.includes(prop.name)
|
|
|
|
// Check if this property should be sortable based on model.sorters
|
|
const isSortable = model.sorters && model.sorters.includes(prop.name)
|
|
|
|
const columnConfig = {
|
|
sorter: isSortable,
|
|
title: prop.label,
|
|
dataIndex: prop.name,
|
|
width: prop.columnWidth || width,
|
|
fixed: fixed,
|
|
key: prop.name,
|
|
render: (text, record) => {
|
|
if (record?.isSkeleton) {
|
|
return (
|
|
<Skeleton.Input active size='small' style={{ width: '100%' }} />
|
|
)
|
|
}
|
|
return (
|
|
<ObjectProperty
|
|
{...prop}
|
|
longId={false}
|
|
objectData={record}
|
|
isEditing={false}
|
|
/>
|
|
)
|
|
}
|
|
}
|
|
|
|
// Add filter configuration if the property is filterable and not in masterFilter
|
|
if (isFilterable && !Object.keys(masterFilter).includes(prop.name)) {
|
|
columnConfig.filterDropdown = ({
|
|
setSelectedKeys,
|
|
selectedKeys,
|
|
confirm,
|
|
clearFilters
|
|
}) =>
|
|
getFilterDropdown({
|
|
setSelectedKeys,
|
|
selectedKeys,
|
|
confirm,
|
|
clearFilters,
|
|
propertyName: prop.label
|
|
})
|
|
// Remove local filtering - let the server handle it
|
|
columnConfig.filtered = false
|
|
}
|
|
|
|
columnsWithSkeleton.push(columnConfig)
|
|
}
|
|
})
|
|
|
|
if (rowActions.length > 0) {
|
|
columnsWithSkeleton.push({
|
|
title: (
|
|
<Flex gap='small' align='center' justify='center'>
|
|
{'Actions'}
|
|
</Flex>
|
|
),
|
|
key: 'actions',
|
|
fixed: 'right',
|
|
width: 80 + rowActions.length * 40, // Adjust width based on number of actions
|
|
render: (record) => {
|
|
return renderActions(record)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Flatten pages array for table display
|
|
const tableData = pages.flatMap((page) => page.items)
|
|
|
|
// Card view rendering
|
|
const cardsContainerRef = useRef(null)
|
|
|
|
// Card view scroll handler
|
|
useEffect(() => {
|
|
if (!cards) return
|
|
const container = cardsContainerRef.current
|
|
if (!container) return
|
|
|
|
const handleCardsScroll = (e) => {
|
|
const { scrollTop, scrollHeight, clientHeight } = e.target
|
|
const lowestPage = Math.min(...pages.map((p) => p.pageNum))
|
|
const prevPage = lowestPage - 1
|
|
|
|
// Load more data when scrolling down
|
|
if (
|
|
scrollHeight - scrollTop - clientHeight < 100 &&
|
|
hasMore &&
|
|
!lazyLoading
|
|
) {
|
|
setTimeout(() => {
|
|
e.target.scrollTop = scrollHeight / 2
|
|
}, 0)
|
|
setLazyLoading(true)
|
|
loadNextPage()
|
|
}
|
|
|
|
// Load previous data when scrolling up
|
|
if (scrollTop < 100 && prevPage > 0 && !lazyLoading) {
|
|
setTimeout(() => {
|
|
e.target.scrollTop = scrollHeight / 2
|
|
}, 0)
|
|
setLazyLoading(true)
|
|
loadPreviousPage()
|
|
}
|
|
}
|
|
|
|
container.addEventListener('scroll', handleCardsScroll)
|
|
return () => container.removeEventListener('scroll', handleCardsScroll)
|
|
}, [cards, pages, hasMore, lazyLoading, loadNextPage, loadPreviousPage])
|
|
|
|
const renderCards = () => {
|
|
return (
|
|
<Row
|
|
gutter={[16, 16]}
|
|
style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
|
|
ref={cardsContainerRef}
|
|
>
|
|
{tableData.map((record) => (
|
|
<Col xs={24} sm={12} md={12} lg={8} xl={6} xxl={6} key={record._id}>
|
|
<Card
|
|
style={{ width: '100%', overflow: 'hidden' }}
|
|
styles={{ body: { padding: 0 } }}
|
|
loading={record.isSkeleton}
|
|
variant={'borderless'}
|
|
>
|
|
<Flex align={'center'} vertical gap={'middle'}>
|
|
<Descriptions
|
|
column={1}
|
|
size='small'
|
|
bordered={true}
|
|
style={{ width: '100%', tableLayout: 'fixed' }}
|
|
className='objectTableDescritions'
|
|
>
|
|
{(() => {
|
|
const descriptionItems = []
|
|
|
|
// Add columns in the order specified by model.columns (same logic as table)
|
|
model.columns.forEach((colName) => {
|
|
const prop = modelProperties.find(
|
|
(p) => p.name === colName
|
|
)
|
|
if (prop) {
|
|
// Check if column should be visible based on visibleColumns prop
|
|
if (
|
|
Object.keys(visibleColumns).length > 0 &&
|
|
visibleColumns[prop.name] === false
|
|
) {
|
|
return // Skip this column if it's not visible
|
|
}
|
|
|
|
descriptionItems.push(
|
|
<Descriptions.Item
|
|
label={prop.label}
|
|
key={prop.name}
|
|
colspan={2}
|
|
>
|
|
<ObjectProperty
|
|
{...prop}
|
|
longId={false}
|
|
objectData={record}
|
|
isEditing={false}
|
|
/>
|
|
</Descriptions.Item>
|
|
)
|
|
}
|
|
})
|
|
|
|
// Add actions if they exist (same as table)
|
|
if (rowActions.length > 0) {
|
|
descriptionItems.push(
|
|
<Descriptions.Item label={'Actions'} key={'actions'}>
|
|
{renderActions(record)}
|
|
</Descriptions.Item>
|
|
)
|
|
}
|
|
|
|
return descriptionItems
|
|
})()}
|
|
</Descriptions>
|
|
</Flex>
|
|
</Card>
|
|
</Col>
|
|
))}
|
|
</Row>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{contextHolder}
|
|
<Flex gap={'middle'} vertical>
|
|
<Table
|
|
ref={tableRef}
|
|
dataSource={tableData}
|
|
columns={columnsWithSkeleton}
|
|
className={cards ? 'dashboard-cards-header' : 'dashboard-table'}
|
|
pagination={false}
|
|
scroll={{ y: adjustedScrollHeight }}
|
|
rowKey='_id'
|
|
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
|
|
onScroll={handleScroll}
|
|
onChange={handleTableChange}
|
|
showSorterTooltip={false}
|
|
style={{ height: '100%' }}
|
|
/>
|
|
{cards ? (
|
|
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
|
{renderCards()}
|
|
</Spin>
|
|
) : null}
|
|
</Flex>
|
|
</>
|
|
)
|
|
}
|
|
)
|
|
|
|
ObjectTable.displayName = 'ObjectTable'
|
|
|
|
ObjectTable.propTypes = {
|
|
type: PropTypes.string.isRequired,
|
|
pageSize: PropTypes.number,
|
|
scrollHeight: PropTypes.string,
|
|
onDataChange: PropTypes.func,
|
|
initialPage: PropTypes.number,
|
|
cards: PropTypes.bool,
|
|
cardRenderer: PropTypes.func,
|
|
visibleColumns: PropTypes.object,
|
|
masterFilter: PropTypes.object
|
|
}
|
|
|
|
export default ObjectTable
|