Enhance ObjectChildTable component by adding detail modal functionality for property editing and viewing. Introduce new icons for edit and view actions, improve property resolution logic, and streamline item removal handling. Update table column rendering to accommodate hidden properties, enhancing user interaction and data management.
This commit is contained in:
parent
9442c9105b
commit
ca0128d986
@ -3,8 +3,11 @@ import PropTypes from 'prop-types'
|
|||||||
import { Table, Skeleton, Card, Button, Flex, Typography, Modal } from 'antd'
|
import { Table, Skeleton, Card, Button, Flex, Typography, Modal } from 'antd'
|
||||||
import PlusIcon from '../../Icons/PlusIcon'
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
import ObjectProperty from './ObjectProperty'
|
import ObjectProperty from './ObjectProperty'
|
||||||
|
import ObjectInfo from './ObjectInfo'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import BinIcon from '../../Icons/BinIcon'
|
import BinIcon from '../../Icons/BinIcon'
|
||||||
|
import EyeIcon from '../../Icons/EyeIcon'
|
||||||
|
import EditIcon from '../../Icons/EditIcon'
|
||||||
const { Text, Link, Title } = Typography
|
const { Text, Link, Title } = Typography
|
||||||
|
|
||||||
const DEFAULT_COLUMN_WIDTHS = {
|
const DEFAULT_COLUMN_WIDTHS = {
|
||||||
@ -64,6 +67,7 @@ const ObjectChildTable = ({
|
|||||||
value = [],
|
value = [],
|
||||||
rollups = [],
|
rollups = [],
|
||||||
onChange,
|
onChange,
|
||||||
|
hiddenPropertyWidth = '300px',
|
||||||
minimal = false,
|
minimal = false,
|
||||||
label = '',
|
label = '',
|
||||||
...tableProps
|
...tableProps
|
||||||
@ -73,6 +77,9 @@ const ObjectChildTable = ({
|
|||||||
const generatedRowKeysRef = useRef(new WeakMap())
|
const generatedRowKeysRef = useRef(new WeakMap())
|
||||||
const generatedRowKeyCountRef = useRef(0)
|
const generatedRowKeyCountRef = useRef(0)
|
||||||
const [minimalModelOpen, setMinimalModelOpen] = useState(false)
|
const [minimalModelOpen, setMinimalModelOpen] = useState(false)
|
||||||
|
const [detailModalOpen, setDetailModalOpen] = useState(false)
|
||||||
|
const [detailModalRecord, setDetailModalRecord] = useState(null)
|
||||||
|
const [detailModalIndex, setDetailModalIndex] = useState(null)
|
||||||
const getFallbackRowKey = (record) => {
|
const getFallbackRowKey = (record) => {
|
||||||
if (!record || typeof record !== 'object') {
|
if (!record || typeof record !== 'object') {
|
||||||
return `object-child-table-row-${String(record)}`
|
return `object-child-table-row-${String(record)}`
|
||||||
@ -124,15 +131,14 @@ const ObjectChildTable = ({
|
|||||||
}, [columns, properties])
|
}, [columns, properties])
|
||||||
|
|
||||||
const resolvedProperties = useMemo(() => {
|
const resolvedProperties = useMemo(() => {
|
||||||
const explicit = orderedPropertyNames
|
const baseProperties =
|
||||||
.map((name) => propertyMap.get(name))
|
columns && columns.length > 0
|
||||||
.filter(Boolean)
|
? orderedPropertyNames
|
||||||
|
.map((name) => propertyMap.get(name))
|
||||||
|
.filter(Boolean)
|
||||||
|
: properties
|
||||||
|
|
||||||
const remaining = properties.filter(
|
return baseProperties.filter((property) => {
|
||||||
(property) => !orderedPropertyNames.includes(property.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
return [...explicit, ...remaining].filter((property) => {
|
|
||||||
if (!property?.name) return false
|
if (!property?.name) return false
|
||||||
if (
|
if (
|
||||||
visibleColumns &&
|
visibleColumns &&
|
||||||
@ -142,13 +148,87 @@ const ObjectChildTable = ({
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}, [orderedPropertyNames, propertyMap, properties, visibleColumns])
|
}, [columns, orderedPropertyNames, propertyMap, properties, visibleColumns])
|
||||||
|
|
||||||
|
const hasHiddenProperties = useMemo(() => {
|
||||||
|
return (
|
||||||
|
Array.isArray(columns) &&
|
||||||
|
columns.length > 0 &&
|
||||||
|
Array.isArray(properties) &&
|
||||||
|
columns.length < properties.length
|
||||||
|
)
|
||||||
|
}, [columns, properties])
|
||||||
|
|
||||||
// When used inside antd Form.Item without Form.List, `value` will be the controlled array.
|
// When used inside antd Form.Item without Form.List, `value` will be the controlled array.
|
||||||
const itemsSource = useMemo(() => {
|
const itemsSource = useMemo(() => {
|
||||||
return value ?? []
|
return value ?? []
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
|
const handleRemoveItem = useCallback(
|
||||||
|
(record, index) => {
|
||||||
|
const currentItems = Array.isArray(itemsSource) ? itemsSource : []
|
||||||
|
|
||||||
|
let newItems
|
||||||
|
if (typeof rowKey === 'string' && record[rowKey] != null) {
|
||||||
|
newItems = currentItems.filter(
|
||||||
|
(item) => item[rowKey] !== record[rowKey]
|
||||||
|
)
|
||||||
|
} else if (typeof rowKey === 'function') {
|
||||||
|
const recordKey = getResolvedRecordKey(record)
|
||||||
|
newItems = currentItems.filter((item) => {
|
||||||
|
const itemKey = getResolvedRecordKey(item)
|
||||||
|
return itemKey !== recordKey
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
newItems = currentItems.filter((_, i) => i !== index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof onChange === 'function') {
|
||||||
|
onChange(newItems)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[itemsSource, onChange, rowKey, getResolvedRecordKey]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleOpenDetailModal = useCallback((record, index) => {
|
||||||
|
setDetailModalRecord({ ...record })
|
||||||
|
setDetailModalIndex(index)
|
||||||
|
setDetailModalOpen(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleDetailPropertyChange = useCallback(
|
||||||
|
(propName, newVal) => {
|
||||||
|
const property = propertyMap.get(propName)
|
||||||
|
const resolved = resolveChangeValue(newVal, property?.type)
|
||||||
|
|
||||||
|
setDetailModalRecord((prev) =>
|
||||||
|
prev ? { ...prev, [propName]: resolved } : prev
|
||||||
|
)
|
||||||
|
|
||||||
|
if (detailModalIndex == null) return
|
||||||
|
|
||||||
|
const currentItems = Array.isArray(itemsSource) ? [...itemsSource] : []
|
||||||
|
const existingRowKey =
|
||||||
|
currentItems[detailModalIndex] != null
|
||||||
|
? getResolvedRecordKey(currentItems[detailModalIndex])
|
||||||
|
: null
|
||||||
|
const updatedItem = {
|
||||||
|
...currentItems[detailModalIndex],
|
||||||
|
[propName]: resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingRowKey) {
|
||||||
|
generatedRowKeysRef.current.set(updatedItem, existingRowKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentItems[detailModalIndex] = updatedItem
|
||||||
|
if (typeof onChange === 'function') {
|
||||||
|
onChange(currentItems)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[propertyMap, detailModalIndex, itemsSource, onChange, getResolvedRecordKey]
|
||||||
|
)
|
||||||
|
|
||||||
const tableColumns = useMemo(() => {
|
const tableColumns = useMemo(() => {
|
||||||
const propertyColumns = resolvedProperties.map((property) => ({
|
const propertyColumns = resolvedProperties.map((property) => ({
|
||||||
title: property.label || property.name,
|
title: property.label || property.name,
|
||||||
@ -197,54 +277,44 @@ const ObjectChildTable = ({
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const deleteColumn =
|
const actionsColumn =
|
||||||
isEditing && canAddRemove
|
hasHiddenProperties || (isEditing && canAddRemove)
|
||||||
? {
|
? {
|
||||||
title: '',
|
title: '',
|
||||||
key: 'delete',
|
key: 'actions',
|
||||||
width: 10,
|
width: hasHiddenProperties && isEditing && canAddRemove ? 70 : 40,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (_text, record, index) => {
|
render: (_text, record, index) => {
|
||||||
if (record?.isSkeleton) {
|
if (record?.isSkeleton) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Flex gap={4}>
|
||||||
type='text'
|
{hasHiddenProperties && (
|
||||||
danger
|
<Button
|
||||||
size='small'
|
type='text'
|
||||||
icon={<BinIcon />}
|
size='small'
|
||||||
onClick={(e) => {
|
icon={isEditing ? <EditIcon /> : <EyeIcon />}
|
||||||
e.stopPropagation()
|
onClick={(e) => {
|
||||||
const currentItems = Array.isArray(itemsSource)
|
e.stopPropagation()
|
||||||
? itemsSource
|
handleOpenDetailModal(record, index)
|
||||||
: []
|
}}
|
||||||
|
/>
|
||||||
// Use record's unique identifier if available, otherwise use index
|
)}
|
||||||
let newItems
|
{isEditing && canAddRemove && (
|
||||||
if (typeof rowKey === 'string' && record[rowKey] != null) {
|
<Button
|
||||||
// Use the unique key to find and remove the item
|
type='text'
|
||||||
newItems = currentItems.filter(
|
danger
|
||||||
(item) => item[rowKey] !== record[rowKey]
|
size='small'
|
||||||
)
|
icon={<BinIcon />}
|
||||||
} else if (typeof rowKey === 'function') {
|
onClick={(e) => {
|
||||||
// If rowKey is a function, find the item by comparing resolved keys.
|
e.stopPropagation()
|
||||||
// Ant Design deprecates index-based rowKey callbacks.
|
handleRemoveItem(record, index)
|
||||||
const recordKey = getResolvedRecordKey(record)
|
}}
|
||||||
newItems = currentItems.filter((item) => {
|
/>
|
||||||
const itemKey = getResolvedRecordKey(item)
|
)}
|
||||||
return itemKey !== recordKey
|
</Flex>
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Fallback to index-based removal
|
|
||||||
newItems = currentItems.filter((_, i) => i !== index)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof onChange === 'function') {
|
|
||||||
onChange(newItems)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,17 +323,20 @@ const ObjectChildTable = ({
|
|||||||
return [
|
return [
|
||||||
...propertyColumns,
|
...propertyColumns,
|
||||||
...additionalColumns,
|
...additionalColumns,
|
||||||
...(deleteColumn ? [deleteColumn] : [])
|
...(actionsColumn ? [actionsColumn] : [])
|
||||||
]
|
]
|
||||||
}, [
|
}, [
|
||||||
resolvedProperties,
|
resolvedProperties,
|
||||||
additionalColumns,
|
additionalColumns,
|
||||||
|
hasHiddenProperties,
|
||||||
isEditing,
|
isEditing,
|
||||||
canAddRemove,
|
canAddRemove,
|
||||||
itemsSource,
|
itemsSource,
|
||||||
onChange,
|
onChange,
|
||||||
rowKey,
|
rowKey,
|
||||||
getResolvedRecordKey
|
getResolvedRecordKey,
|
||||||
|
handleOpenDetailModal,
|
||||||
|
handleRemoveItem
|
||||||
])
|
])
|
||||||
|
|
||||||
const skeletonData = useMemo(() => {
|
const skeletonData = useMemo(() => {
|
||||||
@ -351,8 +424,8 @@ const ObjectChildTable = ({
|
|||||||
}, [properties, rollups, objectData, value, name])
|
}, [properties, rollups, objectData, value, name])
|
||||||
|
|
||||||
const rollupColumns = useMemo(() => {
|
const rollupColumns = useMemo(() => {
|
||||||
const propertyColumns = properties.map((property, index) => {
|
const propertyColumns = resolvedProperties.map((property, index) => {
|
||||||
const nextProperty = properties[index + 1]
|
const nextProperty = resolvedProperties[index + 1]
|
||||||
var nextRollup = null
|
var nextRollup = null
|
||||||
if (nextProperty) {
|
if (nextProperty) {
|
||||||
nextRollup = rollups?.find(
|
nextRollup = rollups?.find(
|
||||||
@ -384,12 +457,12 @@ const ObjectChildTable = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const blankDeleteColumn =
|
const blankActionsColumn =
|
||||||
isEditing && canAddRemove
|
hasHiddenProperties || (isEditing && canAddRemove)
|
||||||
? {
|
? {
|
||||||
title: '',
|
title: '',
|
||||||
key: 'delete',
|
key: 'actions',
|
||||||
width: 40,
|
width: hasHiddenProperties && isEditing && canAddRemove ? 70 : 40,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: () => {
|
render: () => {
|
||||||
return <Flex></Flex>
|
return <Flex></Flex>
|
||||||
@ -398,9 +471,15 @@ const ObjectChildTable = ({
|
|||||||
: null
|
: null
|
||||||
return [
|
return [
|
||||||
...propertyColumns,
|
...propertyColumns,
|
||||||
...(blankDeleteColumn ? [blankDeleteColumn] : [])
|
...(blankActionsColumn ? [blankActionsColumn] : [])
|
||||||
]
|
]
|
||||||
}, [properties, rollups, isEditing, canAddRemove])
|
}, [
|
||||||
|
resolvedProperties,
|
||||||
|
rollups,
|
||||||
|
isEditing,
|
||||||
|
canAddRemove,
|
||||||
|
hasHiddenProperties
|
||||||
|
])
|
||||||
|
|
||||||
const hasRollups = useMemo(
|
const hasRollups = useMemo(
|
||||||
() => Array.isArray(rollups) && rollups.length > 0,
|
() => Array.isArray(rollups) && rollups.length > 0,
|
||||||
@ -495,25 +574,56 @@ const ObjectChildTable = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const detailModal = hasHiddenProperties ? (
|
||||||
|
<Modal
|
||||||
|
open={detailModalOpen}
|
||||||
|
onCancel={() => setDetailModalOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
width='900px'
|
||||||
|
>
|
||||||
|
{label ? (
|
||||||
|
<Title
|
||||||
|
level={2}
|
||||||
|
style={{ marginTop: 0, lineHeight: '0.7', marginBottom: 20 }}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Title>
|
||||||
|
) : null}
|
||||||
|
<ObjectInfo
|
||||||
|
propertyDefinitions={properties}
|
||||||
|
objectData={detailModalRecord}
|
||||||
|
parentData={objectData}
|
||||||
|
labelWidth={hiddenPropertyWidth}
|
||||||
|
isEditing={isEditing}
|
||||||
|
onPropertyChange={isEditing ? handleDetailPropertyChange : undefined}
|
||||||
|
column={1}
|
||||||
|
showLabels={true}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
) : null
|
||||||
|
|
||||||
if (isEditing === true) {
|
if (isEditing === true) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<>
|
||||||
<Flex vertical gap={'middle'}>
|
<Card>
|
||||||
<Flex justify={'space-between'}>
|
<Flex vertical gap={'middle'}>
|
||||||
<Button>Actions</Button>
|
<Flex justify={'space-between'}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
|
||||||
{canAddRemove && (
|
{canAddRemove && (
|
||||||
<Button
|
<Button
|
||||||
type='primary'
|
type='primary'
|
||||||
icon={<PlusIcon />}
|
icon={<PlusIcon />}
|
||||||
onClick={handleAddItem}
|
onClick={handleAddItem}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{tableComponent}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</Card>
|
||||||
{tableComponent}
|
{detailModal}
|
||||||
</Flex>
|
</>
|
||||||
</Card>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,11 +651,17 @@ const ObjectChildTable = ({
|
|||||||
</Title>
|
</Title>
|
||||||
{tableComponent}
|
{tableComponent}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
{detailModal}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tableComponent
|
return (
|
||||||
|
<>
|
||||||
|
{tableComponent}
|
||||||
|
{detailModal}
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectChildTable.propTypes = {
|
ObjectChildTable.propTypes = {
|
||||||
@ -574,7 +690,8 @@ ObjectChildTable.propTypes = {
|
|||||||
rollups: PropTypes.arrayOf(PropTypes.object),
|
rollups: PropTypes.arrayOf(PropTypes.object),
|
||||||
objectData: PropTypes.object,
|
objectData: PropTypes.object,
|
||||||
canAddRemove: PropTypes.bool,
|
canAddRemove: PropTypes.bool,
|
||||||
minimal: PropTypes.bool
|
minimal: PropTypes.bool,
|
||||||
|
hiddenPropertyWidth: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectChildTable
|
export default ObjectChildTable
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user