Added edit mode to tables.

This commit is contained in:
Tom Butcher 2025-12-27 13:50:30 +00:00
parent 38cafdb4a4
commit 50bc816e97

View File

@ -5,6 +5,7 @@ import {
useEffect,
useState,
useCallback,
useMemo,
createElement
} from 'react'
import {
@ -19,7 +20,8 @@ import {
Button,
Input,
Space,
Tooltip
Tooltip,
Form
} from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types'
@ -43,6 +45,46 @@ import { ElectronContext } from '../context/ElectronContext'
const logger = loglevel.getLogger('DasboardTable')
logger.setLevel(config.logLevel)
const RowForm = ({ record, isEditing, onRegister, children }) => {
const [form] = Form.useForm()
useEffect(() => {
if (isEditing && record && !record.isSkeleton) {
form.setFieldsValue(record)
onRegister(record._id, form)
}
return () => {
if (record?._id) onRegister(record._id, null)
}
}, [isEditing, record, form, onRegister])
return (
<Form form={form} component={false}>
{children}
</Form>
)
}
RowForm.propTypes = {
record: PropTypes.object,
isEditing: PropTypes.bool,
onRegister: PropTypes.func,
children: PropTypes.node
}
const EditableRow = ({ record, isEditing, onRegister, ...props }) => {
return (
<RowForm record={record} isEditing={isEditing} onRegister={onRegister}>
<tr {...props} />
</RowForm>
)
}
EditableRow.propTypes = {
record: PropTypes.object,
isEditing: PropTypes.bool,
onRegister: PropTypes.func
}
const ObjectTable = forwardRef(
(
{
@ -54,17 +96,26 @@ const ObjectTable = forwardRef(
cards = false,
visibleColumns = {},
masterFilter = {},
size = 'middle'
size = 'middle',
onStateChange
},
ref
) => {
const { token } = useContext(AuthContext)
const { isElectron } = useContext(ElectronContext)
const onStateChangeRef = useRef(onStateChange)
useEffect(() => {
onStateChangeRef.current = onStateChange
}, [onStateChange])
const {
fetchObjects,
connected,
subscribeToObjectUpdates,
subscribeToObjectTypeUpdates
subscribeToObjectTypeUpdates,
updateMultipleObjects,
lockObject,
unlockObject
} = useContext(ApiServerContext)
const isMobile = useMediaQuery({ maxWidth: 768 })
const navigate = useNavigate()
@ -98,6 +149,21 @@ const ObjectTable = forwardRef(
const [lazyLoading, setLazyLoading] = useState(false)
const [tableData, setTableData] = useState([])
const [isEditing, setIsEditing] = useState(false)
const [editLoading, setEditLoading] = useState(false)
const rowFormsRef = useRef({})
const registerForm = useCallback((id, form) => {
if (form) {
rowFormsRef.current[id] = form
} else {
delete rowFormsRef.current[id]
}
}, [])
useEffect(() => {
onStateChangeRef.current?.({ isEditing, editLoading })
}, [isEditing, editLoading])
const subscribedIdsRef = useRef([])
// const [typeSubscribed, setTypeSubscribed] = useState(false)
const unsubscribesRef = useRef([])
@ -300,6 +366,49 @@ const ObjectTable = forwardRef(
}
}, [fetchData])
const startEditing = useCallback(() => {
setIsEditing(true)
tableData.forEach((item) => {
if (!item.isSkeleton) {
console.log('Locking object:', item)
lockObject(item._id, type)
}
})
}, [tableData, lockObject, type])
const cancelEditing = useCallback(() => {
setIsEditing(false)
tableData.forEach((item) => {
if (!item.isSkeleton) {
unlockObject(item._id, type)
}
})
}, [tableData, unlockObject, type])
const handleUpdate = useCallback(async () => {
setEditLoading(true)
try {
const updates = await Promise.all(
Object.entries(rowFormsRef.current).map(async ([id, form]) => {
const values = await form.validateFields()
return { _id: id, ...values }
})
)
await updateMultipleObjects(type, updates)
setIsEditing(false)
reload()
tableData.forEach((item) => {
if (!item.isSkeleton) {
unlockObject(item._id, type)
}
})
} catch (err) {
console.error(err)
} finally {
setEditLoading(false)
}
}, [type, updateMultipleObjects, reload, tableData, unlockObject])
// Update event handler for real-time updates
const updateEventHandler = useCallback((id, updatedData) => {
setPages((prevPages) =>
@ -316,6 +425,12 @@ const ObjectTable = forwardRef(
}
})
)
console.log('updatedData', updatedData)
if (rowFormsRef.current[id]) {
rowFormsRef.current[id].setFieldsValue(updatedData)
}
}, [])
// Store the latest updateEventHandler in a ref
@ -349,6 +464,9 @@ const ObjectTable = forwardRef(
updateEventHandlerRef.current(itemId, updateData)
}
)
console.log('unsubscribe', unsubscribe)
console.log('subscribedIdsRef', subscribedIdsRef.current)
console.log('unsubscribesRef', unsubscribesRef.current)
subscribedIdsRef.current.push(itemId)
if (unsubscribe) {
unsubscribesRef.current.push(unsubscribe)
@ -408,8 +526,8 @@ const ObjectTable = forwardRef(
}, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])
const updateData = useCallback(
(_id, updatedData) => {
updateEventHandler({ _id, ...updatedData })
(id, updatedData) => {
updateEventHandler(id, updatedData)
},
[updateEventHandler]
)
@ -445,7 +563,12 @@ const ObjectTable = forwardRef(
setData: (newData) => {
setPages([{ pageNum: 1, items: newData }])
},
updateData
updateData,
startEditing,
cancelEditing,
handleUpdate,
isEditing,
editLoading
}))
useEffect(() => {
@ -614,7 +737,7 @@ const ObjectTable = forwardRef(
{...prop}
longId={false}
objectData={record}
isEditing={false}
isEditing={isEditing}
/>
)
}
@ -715,61 +838,71 @@ const ObjectTable = forwardRef(
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 = []
<RowForm
record={record}
isEditing={isEditing}
onRegister={registerForm}
>
<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
// 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={isEditing}
name={prop.name}
/>
</Descriptions.Item>
)
}
})
// Add actions if they exist (same as table)
if (rowActions.length > 0) {
descriptionItems.push(
<Descriptions.Item
label={prop.label}
key={prop.name}
colspan={2}
label={'Actions'}
key={'actions'}
>
<ObjectProperty
{...prop}
longId={false}
objectData={record}
isEditing={false}
/>
{renderActions(record)}
</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>
return descriptionItems
})()}
</Descriptions>
</Flex>
</RowForm>
</Card>
</Col>
))}
@ -777,32 +910,52 @@ const ObjectTable = forwardRef(
)
}
return (
<>
<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%' }}
size={size}
/>
{cards ? (
<Spin indicator={<LoadingOutlined />} spinning={loading}>
{renderCards()}
</Spin>
) : null}
</Flex>
</>
const components = useMemo(
() => ({
body: {
row: EditableRow
}
}),
[]
)
const onRow = useCallback(
(record) => ({
record,
isEditing,
onRegister: registerForm
}),
[isEditing, registerForm]
)
const tableContent = (
<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%' }}
size={size}
components={components}
onRow={onRow}
/>
{cards ? (
<Spin indicator={<LoadingOutlined />} spinning={loading}>
{renderCards()}
</Spin>
) : null}
</Flex>
)
return tableContent
}
)
@ -818,7 +971,8 @@ ObjectTable.propTypes = {
cardRenderer: PropTypes.func,
visibleColumns: PropTypes.object,
masterFilter: PropTypes.object,
size: PropTypes.string
size: PropTypes.string,
onStateChange: PropTypes.func
}
export default ObjectTable