Refactor ObjectCard and ObjectTable components for improved layout and responsiveness
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Introduced a new method to dynamically calculate card column span based on container width in ObjectTable, enhancing responsiveness. - Updated ObjectCard to simplify rendering logic by extracting name and state properties into a separate component. - Added min-width: 0 to relevant CSS classes in App.css to ensure proper layout handling and prevent overflow issues.
This commit is contained in:
parent
c6ad465ad6
commit
3c634a90d0
@ -811,6 +811,7 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
|
|
||||||
.farmcontrol-splitter.ant-splitter-horizontal .ant-splitter-panel {
|
.farmcontrol-splitter.ant-splitter-horizontal .ant-splitter-panel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.farmcontrol-splitter
|
.farmcontrol-splitter
|
||||||
@ -829,6 +830,7 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
.objectTableCardsContainer {
|
.objectTableCardsContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.objectTableCardsContainer .ant-spin-nested-loading {
|
.objectTableCardsContainer .ant-spin-nested-loading {
|
||||||
|
|||||||
@ -66,6 +66,31 @@ const ObjectCard = ({
|
|||||||
? descriptionItems.slice(2)
|
? descriptionItems.slice(2)
|
||||||
: descriptionItems
|
: descriptionItems
|
||||||
|
|
||||||
|
const nameContent = (
|
||||||
|
<>
|
||||||
|
<Flex align='center' gap={12} style={{ minWidth: 0 }}>
|
||||||
|
{modelIcon}
|
||||||
|
<ObjectProperty
|
||||||
|
{...model.properties.find((p) => p.name === 'name')}
|
||||||
|
objectData={record}
|
||||||
|
isEditing={isEditing}
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '600',
|
||||||
|
lineHeight: 1.2,
|
||||||
|
minWidth: 0
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{visibleColumns?.state == true && (
|
||||||
|
<ObjectProperty
|
||||||
|
{...model.properties.find((p) => p.name === 'state')}
|
||||||
|
objectData={record}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
styles={{ body: { padding: 18 } }}
|
styles={{ body: { padding: 18 } }}
|
||||||
@ -88,28 +113,7 @@ const ObjectCard = ({
|
|||||||
size={128}
|
size={128}
|
||||||
/>
|
/>
|
||||||
<Flex vertical gap={10} style={{ minWidth: 0 }}>
|
<Flex vertical gap={10} style={{ minWidth: 0 }}>
|
||||||
{visibleColumns?.name == true && (
|
{visibleColumns?.name == true && nameContent}
|
||||||
<Flex align='center' gap={12} style={{ minWidth: 0 }}>
|
|
||||||
{modelIcon}
|
|
||||||
<ObjectProperty
|
|
||||||
{...model.properties.find((p) => p.name === 'name')}
|
|
||||||
objectData={record}
|
|
||||||
isEditing={isEditing}
|
|
||||||
style={{
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: '600',
|
|
||||||
lineHeight: 1.2,
|
|
||||||
minWidth: 0
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{visibleColumns?.state == true && (
|
|
||||||
<ObjectProperty
|
|
||||||
{...model.properties.find((p) => p.name === 'state')}
|
|
||||||
objectData={record}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
{primaryDescriptionItems.length > 0 && (
|
{primaryDescriptionItems.length > 0 && (
|
||||||
<Descriptions
|
<Descriptions
|
||||||
column={1}
|
column={1}
|
||||||
@ -123,6 +127,7 @@ const ObjectCard = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<Flex vertical gap={8}>
|
<Flex vertical gap={8}>
|
||||||
|
{visibleColumns?.name == true && !thumbnailPresent && nameContent}
|
||||||
{remainingDescriptionItems.length > 0 && (
|
{remainingDescriptionItems.length > 0 && (
|
||||||
<Descriptions
|
<Descriptions
|
||||||
column={1}
|
column={1}
|
||||||
|
|||||||
@ -53,6 +53,14 @@ logger.setLevel(config.logLevel)
|
|||||||
const SCROLL_THRESHOLD = 50
|
const SCROLL_THRESHOLD = 50
|
||||||
const SKELETON_HEIGHT = 49.5
|
const SKELETON_HEIGHT = 49.5
|
||||||
|
|
||||||
|
const getCardColSpan = (containerWidth) => {
|
||||||
|
if (containerWidth >= 2980) return 2
|
||||||
|
if (containerWidth >= 1780) return 4
|
||||||
|
if (containerWidth >= 1080) return 8
|
||||||
|
if (containerWidth >= 680) return 12
|
||||||
|
return 24
|
||||||
|
}
|
||||||
|
|
||||||
const RowForm = ({ record, isEditing, onRegister, children }) => {
|
const RowForm = ({ record, isEditing, onRegister, children }) => {
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1038,13 +1046,68 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Card view rendering
|
// Card view rendering
|
||||||
const cardsContainerRef = useRef(null)
|
const [cardColSpan, setCardColSpan] = useState(24)
|
||||||
|
const cardsContainerNodeRef = useRef(null)
|
||||||
|
const cardsResizeObserverRef = useRef(null)
|
||||||
|
|
||||||
|
const measureCardColSpan = useCallback(() => {
|
||||||
|
const node = cardsContainerNodeRef.current
|
||||||
|
if (!node) return
|
||||||
|
const panel = node.closest('.ant-splitter-panel')
|
||||||
|
const width = (panel ?? node).getBoundingClientRect().width
|
||||||
|
if (width <= 0) return
|
||||||
|
const nextSpan = getCardColSpan(width)
|
||||||
|
setCardColSpan((prev) => (prev === nextSpan ? prev : nextSpan))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const objectTableCardsContainerRef = useCallback(
|
||||||
|
(node) => {
|
||||||
|
cardsResizeObserverRef.current?.disconnect()
|
||||||
|
cardsResizeObserverRef.current = null
|
||||||
|
cardsContainerNodeRef.current = node
|
||||||
|
|
||||||
|
if (!node) return
|
||||||
|
|
||||||
|
let rafId = null
|
||||||
|
const scheduleMeasure = () => {
|
||||||
|
if (rafId != null) cancelAnimationFrame(rafId)
|
||||||
|
rafId = requestAnimationFrame(() => {
|
||||||
|
rafId = null
|
||||||
|
measureCardColSpan()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleMeasure()
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(scheduleMeasure)
|
||||||
|
const panel = node.closest('.ant-splitter-panel')
|
||||||
|
if (panel) {
|
||||||
|
observer.observe(panel)
|
||||||
|
} else {
|
||||||
|
observer.observe(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
cardsResizeObserverRef.current = observer
|
||||||
|
},
|
||||||
|
[measureCardColSpan]
|
||||||
|
)
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!cards) return
|
||||||
|
measureCardColSpan()
|
||||||
|
}, [cards, showFilterSidebar, measureCardColSpan])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => cardsResizeObserverRef.current?.disconnect()
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Card view scroll handler
|
// Card view scroll handler
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!cards) return
|
if (!cards) return
|
||||||
const container = cardsContainerRef.current
|
const container = cardsContainerNodeRef.current
|
||||||
if (!container) return
|
if (!container) return
|
||||||
|
const scrollEl = container.querySelector('.simplebar-content-wrapper')
|
||||||
|
if (!scrollEl) return
|
||||||
|
|
||||||
const handleCardsScroll = (e) => {
|
const handleCardsScroll = (e) => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = e.target
|
const { scrollTop, scrollHeight, clientHeight } = e.target
|
||||||
@ -1056,18 +1119,18 @@ const ObjectTable = forwardRef(
|
|||||||
scrollHeight - scrollTop - clientHeight < 100 &&
|
scrollHeight - scrollTop - clientHeight < 100 &&
|
||||||
!lazyLoading
|
!lazyLoading
|
||||||
) {
|
) {
|
||||||
loadNextPage()
|
loadNextPage(scrollEl)
|
||||||
} else if (
|
} else if (
|
||||||
firstPage?.isSkeletonPage &&
|
firstPage?.isSkeletonPage &&
|
||||||
scrollTop < 100 &&
|
scrollTop < 100 &&
|
||||||
!lazyLoading
|
!lazyLoading
|
||||||
) {
|
) {
|
||||||
loadPreviousPage()
|
loadPreviousPage(scrollEl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container.addEventListener('scroll', handleCardsScroll)
|
scrollEl.addEventListener('scroll', handleCardsScroll)
|
||||||
return () => container.removeEventListener('scroll', handleCardsScroll)
|
return () => scrollEl.removeEventListener('scroll', handleCardsScroll)
|
||||||
}, [cards, pages, lazyLoading, loadNextPage, loadPreviousPage])
|
}, [cards, pages, lazyLoading, loadNextPage, loadPreviousPage])
|
||||||
|
|
||||||
const renderCards = () => {
|
const renderCards = () => {
|
||||||
@ -1078,22 +1141,13 @@ const ObjectTable = forwardRef(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='objectTableCards'>
|
<div className='objectTableCards'>
|
||||||
<Row gutter={[16, 16]} ref={cardsContainerRef}>
|
<Row gutter={[16, 16]} key={cardColSpan}>
|
||||||
{tableData.map((record) => {
|
{tableData.map((record) => {
|
||||||
if (record?._id == undefined) {
|
if (record?._id == undefined) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Col
|
<Col span={cardColSpan} key={record._id}>
|
||||||
xs={24}
|
|
||||||
sm={24}
|
|
||||||
md={24}
|
|
||||||
lg={12}
|
|
||||||
xl={12}
|
|
||||||
xxl={8}
|
|
||||||
xxxl={6}
|
|
||||||
key={record._id}
|
|
||||||
>
|
|
||||||
<div style={{ width: '100%', overflow: 'hidden' }}>
|
<div style={{ width: '100%', overflow: 'hidden' }}>
|
||||||
<RowForm
|
<RowForm
|
||||||
record={record}
|
record={record}
|
||||||
@ -1141,11 +1195,22 @@ const ObjectTable = forwardRef(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const tableContent = (
|
const tableContent = (
|
||||||
<Flex gap={'middle'} vertical style={{ flex: 1, minWidth: 0 }}>
|
<Flex
|
||||||
<Splitter className={'farmcontrol-splitter'}>
|
gap={'middle'}
|
||||||
|
vertical
|
||||||
|
style={{ flex: 1, minWidth: 0, minHeight: 0 }}
|
||||||
|
>
|
||||||
|
<Splitter
|
||||||
|
className={'farmcontrol-splitter'}
|
||||||
|
onResize={measureCardColSpan}
|
||||||
|
onResizeEnd={measureCardColSpan}
|
||||||
|
>
|
||||||
<Splitter.Panel>
|
<Splitter.Panel>
|
||||||
{cards ? (
|
{cards ? (
|
||||||
<div className='objectTableCardsContainer'>
|
<div
|
||||||
|
className='objectTableCardsContainer'
|
||||||
|
ref={objectTableCardsContainerRef}
|
||||||
|
>
|
||||||
<Spin
|
<Spin
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
spinning={loading}
|
spinning={loading}
|
||||||
|
|||||||
@ -68,6 +68,13 @@ export const User = {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 180
|
columnWidth: 180
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
@ -76,12 +83,13 @@ export const User = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
columnWidth: 200
|
columnWidth: 200
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'updatedAt',
|
name: 'username',
|
||||||
label: 'Updated At',
|
label: 'Username',
|
||||||
type: 'dateTime',
|
required: true,
|
||||||
readOnly: true,
|
type: 'text',
|
||||||
columnWidth: 175
|
columnWidth: 150
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -90,13 +98,7 @@ export const User = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
columnWidth: 130
|
columnWidth: 130
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'username',
|
|
||||||
label: 'Username',
|
|
||||||
required: true,
|
|
||||||
type: 'text',
|
|
||||||
columnWidth: 150
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'lastName',
|
name: 'lastName',
|
||||||
label: 'Last Name',
|
label: 'Last Name',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user