Added edit mode to tables.
This commit is contained in:
parent
38cafdb4a4
commit
50bc816e97
@ -5,6 +5,7 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useMemo,
|
||||||
createElement
|
createElement
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
@ -19,7 +20,8 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
Space,
|
Space,
|
||||||
Tooltip
|
Tooltip,
|
||||||
|
Form
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
@ -43,6 +45,46 @@ import { ElectronContext } from '../context/ElectronContext'
|
|||||||
const logger = loglevel.getLogger('DasboardTable')
|
const logger = loglevel.getLogger('DasboardTable')
|
||||||
logger.setLevel(config.logLevel)
|
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(
|
const ObjectTable = forwardRef(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -54,17 +96,26 @@ const ObjectTable = forwardRef(
|
|||||||
cards = false,
|
cards = false,
|
||||||
visibleColumns = {},
|
visibleColumns = {},
|
||||||
masterFilter = {},
|
masterFilter = {},
|
||||||
size = 'middle'
|
size = 'middle',
|
||||||
|
onStateChange
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const { token } = useContext(AuthContext)
|
const { token } = useContext(AuthContext)
|
||||||
const { isElectron } = useContext(ElectronContext)
|
const { isElectron } = useContext(ElectronContext)
|
||||||
|
const onStateChangeRef = useRef(onStateChange)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onStateChangeRef.current = onStateChange
|
||||||
|
}, [onStateChange])
|
||||||
const {
|
const {
|
||||||
fetchObjects,
|
fetchObjects,
|
||||||
connected,
|
connected,
|
||||||
subscribeToObjectUpdates,
|
subscribeToObjectUpdates,
|
||||||
subscribeToObjectTypeUpdates
|
subscribeToObjectTypeUpdates,
|
||||||
|
updateMultipleObjects,
|
||||||
|
lockObject,
|
||||||
|
unlockObject
|
||||||
} = useContext(ApiServerContext)
|
} = useContext(ApiServerContext)
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -98,6 +149,21 @@ const ObjectTable = forwardRef(
|
|||||||
const [lazyLoading, setLazyLoading] = useState(false)
|
const [lazyLoading, setLazyLoading] = useState(false)
|
||||||
const [tableData, setTableData] = useState([])
|
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 subscribedIdsRef = useRef([])
|
||||||
// const [typeSubscribed, setTypeSubscribed] = useState(false)
|
// const [typeSubscribed, setTypeSubscribed] = useState(false)
|
||||||
const unsubscribesRef = useRef([])
|
const unsubscribesRef = useRef([])
|
||||||
@ -300,6 +366,49 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
}, [fetchData])
|
}, [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
|
// Update event handler for real-time updates
|
||||||
const updateEventHandler = useCallback((id, updatedData) => {
|
const updateEventHandler = useCallback((id, updatedData) => {
|
||||||
setPages((prevPages) =>
|
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
|
// Store the latest updateEventHandler in a ref
|
||||||
@ -349,6 +464,9 @@ const ObjectTable = forwardRef(
|
|||||||
updateEventHandlerRef.current(itemId, updateData)
|
updateEventHandlerRef.current(itemId, updateData)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
console.log('unsubscribe', unsubscribe)
|
||||||
|
console.log('subscribedIdsRef', subscribedIdsRef.current)
|
||||||
|
console.log('unsubscribesRef', unsubscribesRef.current)
|
||||||
subscribedIdsRef.current.push(itemId)
|
subscribedIdsRef.current.push(itemId)
|
||||||
if (unsubscribe) {
|
if (unsubscribe) {
|
||||||
unsubscribesRef.current.push(unsubscribe)
|
unsubscribesRef.current.push(unsubscribe)
|
||||||
@ -408,8 +526,8 @@ const ObjectTable = forwardRef(
|
|||||||
}, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])
|
}, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])
|
||||||
|
|
||||||
const updateData = useCallback(
|
const updateData = useCallback(
|
||||||
(_id, updatedData) => {
|
(id, updatedData) => {
|
||||||
updateEventHandler({ _id, ...updatedData })
|
updateEventHandler(id, updatedData)
|
||||||
},
|
},
|
||||||
[updateEventHandler]
|
[updateEventHandler]
|
||||||
)
|
)
|
||||||
@ -445,7 +563,12 @@ const ObjectTable = forwardRef(
|
|||||||
setData: (newData) => {
|
setData: (newData) => {
|
||||||
setPages([{ pageNum: 1, items: newData }])
|
setPages([{ pageNum: 1, items: newData }])
|
||||||
},
|
},
|
||||||
updateData
|
updateData,
|
||||||
|
startEditing,
|
||||||
|
cancelEditing,
|
||||||
|
handleUpdate,
|
||||||
|
isEditing,
|
||||||
|
editLoading
|
||||||
}))
|
}))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -614,7 +737,7 @@ const ObjectTable = forwardRef(
|
|||||||
{...prop}
|
{...prop}
|
||||||
longId={false}
|
longId={false}
|
||||||
objectData={record}
|
objectData={record}
|
||||||
isEditing={false}
|
isEditing={isEditing}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -714,6 +837,11 @@ const ObjectTable = forwardRef(
|
|||||||
styles={{ body: { padding: 0 } }}
|
styles={{ body: { padding: 0 } }}
|
||||||
loading={record.isSkeleton}
|
loading={record.isSkeleton}
|
||||||
variant={'borderless'}
|
variant={'borderless'}
|
||||||
|
>
|
||||||
|
<RowForm
|
||||||
|
record={record}
|
||||||
|
isEditing={isEditing}
|
||||||
|
onRegister={registerForm}
|
||||||
>
|
>
|
||||||
<Flex align={'center'} vertical gap={'middle'}>
|
<Flex align={'center'} vertical gap={'middle'}>
|
||||||
<Descriptions
|
<Descriptions
|
||||||
@ -750,7 +878,8 @@ const ObjectTable = forwardRef(
|
|||||||
{...prop}
|
{...prop}
|
||||||
longId={false}
|
longId={false}
|
||||||
objectData={record}
|
objectData={record}
|
||||||
isEditing={false}
|
isEditing={isEditing}
|
||||||
|
name={prop.name}
|
||||||
/>
|
/>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
)
|
)
|
||||||
@ -760,7 +889,10 @@ const ObjectTable = forwardRef(
|
|||||||
// Add actions if they exist (same as table)
|
// Add actions if they exist (same as table)
|
||||||
if (rowActions.length > 0) {
|
if (rowActions.length > 0) {
|
||||||
descriptionItems.push(
|
descriptionItems.push(
|
||||||
<Descriptions.Item label={'Actions'} key={'actions'}>
|
<Descriptions.Item
|
||||||
|
label={'Actions'}
|
||||||
|
key={'actions'}
|
||||||
|
>
|
||||||
{renderActions(record)}
|
{renderActions(record)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
)
|
)
|
||||||
@ -770,6 +902,7 @@ const ObjectTable = forwardRef(
|
|||||||
})()}
|
})()}
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</RowForm>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
@ -777,8 +910,25 @@ const ObjectTable = forwardRef(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const components = useMemo(
|
||||||
<>
|
() => ({
|
||||||
|
body: {
|
||||||
|
row: EditableRow
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onRow = useCallback(
|
||||||
|
(record) => ({
|
||||||
|
record,
|
||||||
|
isEditing,
|
||||||
|
onRegister: registerForm
|
||||||
|
}),
|
||||||
|
[isEditing, registerForm]
|
||||||
|
)
|
||||||
|
|
||||||
|
const tableContent = (
|
||||||
<Flex gap={'middle'} vertical>
|
<Flex gap={'middle'} vertical>
|
||||||
<Table
|
<Table
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
@ -794,6 +944,8 @@ const ObjectTable = forwardRef(
|
|||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
size={size}
|
size={size}
|
||||||
|
components={components}
|
||||||
|
onRow={onRow}
|
||||||
/>
|
/>
|
||||||
{cards ? (
|
{cards ? (
|
||||||
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
||||||
@ -801,8 +953,9 @@ const ObjectTable = forwardRef(
|
|||||||
</Spin>
|
</Spin>
|
||||||
) : null}
|
) : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return tableContent
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -818,7 +971,8 @@ ObjectTable.propTypes = {
|
|||||||
cardRenderer: PropTypes.func,
|
cardRenderer: PropTypes.func,
|
||||||
visibleColumns: PropTypes.object,
|
visibleColumns: PropTypes.object,
|
||||||
masterFilter: PropTypes.object,
|
masterFilter: PropTypes.object,
|
||||||
size: PropTypes.string
|
size: PropTypes.string,
|
||||||
|
onStateChange: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectTable
|
export default ObjectTable
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user