diff --git a/src/components/Dashboard/common/ObjectTable.jsx b/src/components/Dashboard/common/ObjectTable.jsx index fe142f3..e1257ed 100644 --- a/src/components/Dashboard/common/ObjectTable.jsx +++ b/src/components/Dashboard/common/ObjectTable.jsx @@ -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 ( +
+ {children} +
+ ) +} + +RowForm.propTypes = { + record: PropTypes.object, + isEditing: PropTypes.bool, + onRegister: PropTypes.func, + children: PropTypes.node +} + +const EditableRow = ({ record, isEditing, onRegister, ...props }) => { + return ( + + + + ) +} + +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'} > - - - {(() => { - const descriptionItems = [] + + + + {(() => { + 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( + + + + ) } + }) + // Add actions if they exist (same as table) + if (rowActions.length > 0) { descriptionItems.push( - + {renderActions(record)} ) } - }) - // Add actions if they exist (same as table) - if (rowActions.length > 0) { - descriptionItems.push( - - {renderActions(record)} - - ) - } - - return descriptionItems - })()} - - + return descriptionItems + })()} + + + ))} @@ -777,32 +910,52 @@ const ObjectTable = forwardRef( ) } - return ( - <> - - }} - onScroll={handleScroll} - onChange={handleTableChange} - showSorterTooltip={false} - style={{ height: '100%' }} - size={size} - /> - {cards ? ( - } spinning={loading}> - {renderCards()} - - ) : null} - - + const components = useMemo( + () => ({ + body: { + row: EditableRow + } + }), + [] ) + + const onRow = useCallback( + (record) => ({ + record, + isEditing, + onRegister: registerForm + }), + [isEditing, registerForm] + ) + + const tableContent = ( + +
}} + onScroll={handleScroll} + onChange={handleTableChange} + showSorterTooltip={false} + style={{ height: '100%' }} + size={size} + components={components} + onRow={onRow} + /> + {cards ? ( + } spinning={loading}> + {renderCards()} + + ) : null} + + ) + + 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