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 PlusIcon from '../../Icons/PlusIcon'
|
||||
import ObjectProperty from './ObjectProperty'
|
||||
import ObjectInfo from './ObjectInfo'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import BinIcon from '../../Icons/BinIcon'
|
||||
import EyeIcon from '../../Icons/EyeIcon'
|
||||
import EditIcon from '../../Icons/EditIcon'
|
||||
const { Text, Link, Title } = Typography
|
||||
|
||||
const DEFAULT_COLUMN_WIDTHS = {
|
||||
@ -64,6 +67,7 @@ const ObjectChildTable = ({
|
||||
value = [],
|
||||
rollups = [],
|
||||
onChange,
|
||||
hiddenPropertyWidth = '300px',
|
||||
minimal = false,
|
||||
label = '',
|
||||
...tableProps
|
||||
@ -73,6 +77,9 @@ const ObjectChildTable = ({
|
||||
const generatedRowKeysRef = useRef(new WeakMap())
|
||||
const generatedRowKeyCountRef = useRef(0)
|
||||
const [minimalModelOpen, setMinimalModelOpen] = useState(false)
|
||||
const [detailModalOpen, setDetailModalOpen] = useState(false)
|
||||
const [detailModalRecord, setDetailModalRecord] = useState(null)
|
||||
const [detailModalIndex, setDetailModalIndex] = useState(null)
|
||||
const getFallbackRowKey = (record) => {
|
||||
if (!record || typeof record !== 'object') {
|
||||
return `object-child-table-row-${String(record)}`
|
||||
@ -124,15 +131,14 @@ const ObjectChildTable = ({
|
||||
}, [columns, properties])
|
||||
|
||||
const resolvedProperties = useMemo(() => {
|
||||
const explicit = orderedPropertyNames
|
||||
const baseProperties =
|
||||
columns && columns.length > 0
|
||||
? orderedPropertyNames
|
||||
.map((name) => propertyMap.get(name))
|
||||
.filter(Boolean)
|
||||
: properties
|
||||
|
||||
const remaining = properties.filter(
|
||||
(property) => !orderedPropertyNames.includes(property.name)
|
||||
)
|
||||
|
||||
return [...explicit, ...remaining].filter((property) => {
|
||||
return baseProperties.filter((property) => {
|
||||
if (!property?.name) return false
|
||||
if (
|
||||
visibleColumns &&
|
||||
@ -142,13 +148,87 @@ const ObjectChildTable = ({
|
||||
}
|
||||
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.
|
||||
const itemsSource = useMemo(() => {
|
||||
return 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 propertyColumns = resolvedProperties.map((property) => ({
|
||||
title: property.label || property.name,
|
||||
@ -197,18 +277,32 @@ const ObjectChildTable = ({
|
||||
}
|
||||
}))
|
||||
|
||||
const deleteColumn =
|
||||
isEditing && canAddRemove
|
||||
const actionsColumn =
|
||||
hasHiddenProperties || (isEditing && canAddRemove)
|
||||
? {
|
||||
title: '',
|
||||
key: 'delete',
|
||||
width: 10,
|
||||
key: 'actions',
|
||||
width: hasHiddenProperties && isEditing && canAddRemove ? 70 : 40,
|
||||
fixed: 'right',
|
||||
render: (_text, record, index) => {
|
||||
if (record?.isSkeleton) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
{hasHiddenProperties && (
|
||||
<Button
|
||||
type='text'
|
||||
size='small'
|
||||
icon={isEditing ? <EditIcon /> : <EyeIcon />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleOpenDetailModal(record, index)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isEditing && canAddRemove && (
|
||||
<Button
|
||||
type='text'
|
||||
danger
|
||||
@ -216,35 +310,11 @@ const ObjectChildTable = ({
|
||||
icon={<BinIcon />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
const currentItems = Array.isArray(itemsSource)
|
||||
? itemsSource
|
||||
: []
|
||||
|
||||
// Use record's unique identifier if available, otherwise use index
|
||||
let newItems
|
||||
if (typeof rowKey === 'string' && record[rowKey] != null) {
|
||||
// Use the unique key to find and remove the item
|
||||
newItems = currentItems.filter(
|
||||
(item) => item[rowKey] !== record[rowKey]
|
||||
)
|
||||
} else if (typeof rowKey === 'function') {
|
||||
// If rowKey is a function, find the item by comparing resolved keys.
|
||||
// Ant Design deprecates index-based rowKey callbacks.
|
||||
const recordKey = getResolvedRecordKey(record)
|
||||
newItems = currentItems.filter((item) => {
|
||||
const itemKey = getResolvedRecordKey(item)
|
||||
return itemKey !== recordKey
|
||||
})
|
||||
} else {
|
||||
// Fallback to index-based removal
|
||||
newItems = currentItems.filter((_, i) => i !== index)
|
||||
}
|
||||
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(newItems)
|
||||
}
|
||||
handleRemoveItem(record, index)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -253,17 +323,20 @@ const ObjectChildTable = ({
|
||||
return [
|
||||
...propertyColumns,
|
||||
...additionalColumns,
|
||||
...(deleteColumn ? [deleteColumn] : [])
|
||||
...(actionsColumn ? [actionsColumn] : [])
|
||||
]
|
||||
}, [
|
||||
resolvedProperties,
|
||||
additionalColumns,
|
||||
hasHiddenProperties,
|
||||
isEditing,
|
||||
canAddRemove,
|
||||
itemsSource,
|
||||
onChange,
|
||||
rowKey,
|
||||
getResolvedRecordKey
|
||||
getResolvedRecordKey,
|
||||
handleOpenDetailModal,
|
||||
handleRemoveItem
|
||||
])
|
||||
|
||||
const skeletonData = useMemo(() => {
|
||||
@ -351,8 +424,8 @@ const ObjectChildTable = ({
|
||||
}, [properties, rollups, objectData, value, name])
|
||||
|
||||
const rollupColumns = useMemo(() => {
|
||||
const propertyColumns = properties.map((property, index) => {
|
||||
const nextProperty = properties[index + 1]
|
||||
const propertyColumns = resolvedProperties.map((property, index) => {
|
||||
const nextProperty = resolvedProperties[index + 1]
|
||||
var nextRollup = null
|
||||
if (nextProperty) {
|
||||
nextRollup = rollups?.find(
|
||||
@ -384,12 +457,12 @@ const ObjectChildTable = ({
|
||||
}
|
||||
}
|
||||
})
|
||||
const blankDeleteColumn =
|
||||
isEditing && canAddRemove
|
||||
const blankActionsColumn =
|
||||
hasHiddenProperties || (isEditing && canAddRemove)
|
||||
? {
|
||||
title: '',
|
||||
key: 'delete',
|
||||
width: 40,
|
||||
key: 'actions',
|
||||
width: hasHiddenProperties && isEditing && canAddRemove ? 70 : 40,
|
||||
fixed: 'right',
|
||||
render: () => {
|
||||
return <Flex></Flex>
|
||||
@ -398,9 +471,15 @@ const ObjectChildTable = ({
|
||||
: null
|
||||
return [
|
||||
...propertyColumns,
|
||||
...(blankDeleteColumn ? [blankDeleteColumn] : [])
|
||||
...(blankActionsColumn ? [blankActionsColumn] : [])
|
||||
]
|
||||
}, [properties, rollups, isEditing, canAddRemove])
|
||||
}, [
|
||||
resolvedProperties,
|
||||
rollups,
|
||||
isEditing,
|
||||
canAddRemove,
|
||||
hasHiddenProperties
|
||||
])
|
||||
|
||||
const hasRollups = useMemo(
|
||||
() => Array.isArray(rollups) && rollups.length > 0,
|
||||
@ -495,8 +574,37 @@ const ObjectChildTable = ({
|
||||
</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) {
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<Flex vertical gap={'middle'}>
|
||||
<Flex justify={'space-between'}>
|
||||
@ -514,6 +622,8 @@ const ObjectChildTable = ({
|
||||
{tableComponent}
|
||||
</Flex>
|
||||
</Card>
|
||||
{detailModal}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -541,11 +651,17 @@ const ObjectChildTable = ({
|
||||
</Title>
|
||||
{tableComponent}
|
||||
</Modal>
|
||||
{detailModal}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return tableComponent
|
||||
return (
|
||||
<>
|
||||
{tableComponent}
|
||||
{detailModal}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
ObjectChildTable.propTypes = {
|
||||
@ -574,7 +690,8 @@ ObjectChildTable.propTypes = {
|
||||
rollups: PropTypes.arrayOf(PropTypes.object),
|
||||
objectData: PropTypes.object,
|
||||
canAddRemove: PropTypes.bool,
|
||||
minimal: PropTypes.bool
|
||||
minimal: PropTypes.bool,
|
||||
hiddenPropertyWidth: PropTypes.string
|
||||
}
|
||||
|
||||
export default ObjectChildTable
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user