Fixed warnings and info pages.

This commit is contained in:
Tom Butcher 2025-08-23 00:53:47 +01:00
parent bb047651ae
commit 5680b067a8
18 changed files with 1567 additions and 1675 deletions

View File

@ -1,3 +1,4 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd' import { Space, Flex, Card } from 'antd'
import useCollapseState from '../../hooks/useCollapseState' import useCollapseState from '../../hooks/useCollapseState'
@ -19,6 +20,8 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder'
const FilamentStockInfo = () => { const FilamentStockInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const filamentStockId = new URLSearchParams(location.search).get( const filamentStockId = new URLSearchParams(location.search).get(
'filamentStockId' 'filamentStockId'
) )
@ -31,161 +34,166 @@ const FilamentStockInfo = () => {
auditLogs: true auditLogs: true
} }
) )
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
return true
}
}
return ( return (
<EditObjectForm <>
id={filamentStockId} <Flex
type='filamentStock' gap='large'
style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }} vertical='true'
> style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }}
{({ >
loading, <Flex justify={'space-between'}>
isEditing, <Space size='middle'>
formValid, <Space size='small'>
objectData, <ObjectActions
editLoading, type='filamentStock'
lock, id={filamentStockId}
fetchObject disabled={editFormState.loading}
}) => { />
const actions = { <ViewButton
reload: () => { disabled={editFormState.loading}
fetchObject() items={[
return true { key: 'info', label: 'Filament Stock Information' },
} { key: 'events', label: 'Filament Stock Events' },
} { key: 'notes', label: 'Notes' },
return ( { key: 'auditLogs', label: 'Audit Logs' }
<ActionHandler actions={actions} loading={loading}> ]}
{({ callAction }) => ( visibleState={collapseState}
<Flex updateVisibleState={updateCollapseState}
gap='large' />
vertical='true' </Space>
style={{ height: '100%', minHeight: 0 }} <LockIndicator lock={editFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
/>
</Space>
</Flex>
<div style={{ height: '100%', overflow: 'auto' }}>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Filament Stock Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={filamentStockId}
<Space size='small'> type='filamentStock'
<ObjectActions style={{ height: '100%' }}
type='filamentStock' ref={editFormRef}
id={filamentStockId} onStateChange={(state) => {
disabled={loading} setEditFormState((prev) => ({ ...prev, ...state }))
/> }}
<ViewButton >
disabled={loading} {({ loading, isEditing, objectData }) => (
items={[ <ObjectInfo
{ key: 'info', label: 'Filament Stock Information' }, loading={loading}
{ key: 'events', label: 'Filament Stock Events' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing} isEditing={isEditing}
handleUpdate={() => { type='filamentStock'
callAction('finishEdit') objectData={objectData}
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={true}
loading={editLoading}
/> />
</Space> )}
</Flex> </EditObjectForm>
</InfoCollapse>
<div style={{ height: '100%', overflow: 'auto' }}> </ActionHandler>
<Flex vertical gap={'large'}> <InfoCollapse
<InfoCollapse title='Filament Stock Events'
title='Filament Stock Information' icon={<FilamentStockIcon />}
icon={<InfoCircleIcon />} active={collapseState.events}
active={collapseState.info} onToggle={(expanded) => updateCollapseState('events', expanded)}
onToggle={(expanded) => collapseKey='events'
updateCollapseState('info', expanded) >
} {editFormState.loading ? (
collapseKey='info' <InfoCollapsePlaceholder />
> ) : (
<ObjectInfo <ObjectTable
loading={loading} type='stockEvent'
isEditing={isEditing} masterFilter={{ 'parent._id': filamentStockId }}
type='filamentStock' visibleColumns={{ 'parent._id': false }}
objectData={objectData} />
/> )}
</InfoCollapse> </InfoCollapse>
<InfoCollapse
<InfoCollapse title='Notes'
title='Filament Stock Events' icon={<NoteIcon />}
icon={<FilamentStockIcon />} active={collapseState.notes}
active={collapseState.events} onToggle={(expanded) => updateCollapseState('notes', expanded)}
onToggle={(expanded) => collapseKey='notes'
updateCollapseState('events', expanded) >
} <Card>
collapseKey='events' <NotesPanel _id={filamentStockId} type='filamentStock' />
> </Card>
{loading ? ( </InfoCollapse>
<InfoCollapsePlaceholder /> <InfoCollapse
) : ( title='Audit Logs'
<ObjectTable icon={<AuditLogIcon />}
type='stockEvent' active={collapseState.auditLogs}
masterFilter={{ 'parent._id': filamentStockId }} onToggle={(expanded) =>
visibleColumns={{ 'parent._id': false }} updateCollapseState('auditLogs', expanded)
/> }
)} collapseKey='auditLogs'
</InfoCollapse> >
{editFormState.loading ? (
<InfoCollapse <InfoCollapsePlaceholder />
title='Notes' ) : (
icon={<NoteIcon />} <ObjectTable
active={collapseState.notes} type='auditLog'
onToggle={(expanded) => masterFilter={{ 'parent._id': filamentStockId }}
updateCollapseState('notes', expanded) visibleColumns={{ _id: false, 'parent._id': false }}
} />
collapseKey='notes' )}
> </InfoCollapse>
<Card> </Flex>
<NotesPanel </div>
_id={filamentStockId} </Flex>
type='filamentStock' </>
/>
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': filamentStockId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
)}
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -14,9 +14,7 @@ const DocumentSizes = () => {
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const [newDocumentSizeOpen, setNewDocumentSizeOpen] = useState(false) const [newDocumentSizeOpen, setNewDocumentSizeOpen] = useState(false)
const tableRef = useRef() const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('documentSize') const [viewMode, setViewMode] = useViewMode('documentSize')
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
useColumnVisibility('documentSize') useColumnVisibility('documentSize')
@ -85,7 +83,7 @@ const DocumentSizes = () => {
<NewDocumentSize <NewDocumentSize
onOk={() => { onOk={() => {
setNewDocumentSizeOpen(false) setNewDocumentSizeOpen(false)
messageApi.success('New note type created successfully.') messageApi.success('New document size created successfully.')
tableRef.current?.reload() tableRef.current?.reload()
}} }}
reset={!newDocumentSizeOpen} reset={!newDocumentSizeOpen}

View File

@ -1,3 +1,4 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd' import { Space, Flex, Card } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
@ -24,6 +25,8 @@ log.setLevel(config.logLevel)
const DocumentSizeInfo = () => { const DocumentSizeInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const documentSizeId = new URLSearchParams(location.search).get( const documentSizeId = new URLSearchParams(location.search).get(
'documentSizeId' 'documentSizeId'
) )
@ -35,159 +38,149 @@ const DocumentSizeInfo = () => {
auditLogs: true auditLogs: true
} }
) )
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
return true
}
}
return ( return (
<EditObjectForm <>
id={documentSizeId} <Flex
type='documentSize' gap='large'
style={{ height: '100%' }} vertical='true'
> style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }}
{({ >
loading, <Flex justify={'space-between'}>
isEditing, <Space size='middle'>
startEditing, <Space size='small'>
cancelEditing, <ObjectActions
handleUpdate, type='documentSize'
formValid, id={documentSizeId}
objectData, disabled={editFormState.loading}
editLoading, />
lock, <ViewButton
fetchObject disabled={editFormState.loading}
}) => { items={[
// Define actions for ActionHandler { key: 'info', label: 'Document Size Information' },
const actions = { { key: 'notes', label: 'Notes' },
reload: () => { { key: 'auditLogs', label: 'Audit Logs' }
fetchObject() ]}
return true visibleState={collapseState}
}, updateVisibleState={updateCollapseState}
edit: () => { />
startEditing() </Space>
return false <LockIndicator lock={editFormState.lock} />
}, </Space>
cancelEdit: () => { <Space>
cancelEditing() <EditButtons
return true isEditing={editFormState.isEditing}
}, handleUpdate={() => {
finishEdit: () => { actionHandlerRef.current.callAction('finishEdit')
handleUpdate() }}
return true cancelEditing={() => {
} actionHandlerRef.current.callAction('cancelEdit')
} }}
startEditing={() => {
return ( actionHandlerRef.current.callAction('edit')
<ActionHandler actions={actions} loading={loading}> }}
{({ callAction }) => ( editLoading={editFormState.editLoading}
<Flex formValid={editFormState.formValid}
gap='large' disabled={editFormState.lock?.locked || editFormState.loading}
vertical='true' loading={editFormState.editLoading}
style={{ />
height: 'calc(var(--unit-100vh) - 155px)', </Space>
minHeight: 0 </Flex>
}} <div style={{ height: '100%', overflow: 'auto' }}>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Document Size Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={documentSizeId}
<Space size='small'> type='documentSize'
<ObjectActions style={{ height: '100%' }}
type='documentSize' ref={editFormRef}
id={documentSizeId} onStateChange={(state) => {
disabled={loading} setEditFormState((prev) => ({ ...prev, ...state }))
/> }}
<ViewButton >
disabled={loading} {({ loading, isEditing, objectData }) => (
items={[ <ObjectInfo
{ key: 'info', label: 'DocumentSize Information' }, loading={loading}
{ key: 'stocks', label: 'DocumentSize Stocks' }, indicator={<LoadingOutlined />}
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing} isEditing={isEditing}
handleUpdate={() => { type='documentSize'
callAction('finishEdit') objectData={objectData}
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={lock?.locked || loading}
loading={editLoading}
/> />
</Space> )}
</Flex> </EditObjectForm>
</InfoCollapse>
<div style={{ height: '100%', overflowY: 'scroll' }}> </ActionHandler>
<Flex vertical gap={'large'}> <InfoCollapse
<InfoCollapse title='Notes'
title='Document Size Information' icon={<NoteIcon />}
icon={<InfoCircleIcon />} active={collapseState.notes}
active={collapseState.info} onToggle={(expanded) => updateCollapseState('notes', expanded)}
onToggle={(expanded) => collapseKey='notes'
updateCollapseState('info', expanded) >
} <Card>
collapseKey='info' <NotesPanel _id={documentSizeId} type='documentSize' />
> </Card>
<ObjectInfo </InfoCollapse>
loading={loading} <InfoCollapse
indicator={<LoadingOutlined />} title='Audit Logs'
isEditing={isEditing} icon={<AuditLogIcon />}
type='documentSize' active={collapseState.auditLogs}
objectData={objectData} onToggle={(expanded) =>
/> updateCollapseState('auditLogs', expanded)
</InfoCollapse> }
collapseKey='auditLogs'
<InfoCollapse >
title='Notes' {editFormState.loading ? (
icon={<NoteIcon />} <InfoCollapsePlaceholder />
active={collapseState.notes} ) : (
onToggle={(expanded) => <ObjectTable
updateCollapseState('notes', expanded) type='auditLog'
} masterFilter={{ 'parent._id': documentSizeId }}
collapseKey='notes' visibleColumns={{ _id: false, 'parent._id': false }}
> />
<Card> )}
<NotesPanel _id={documentSizeId} type='documentSize' /> </InfoCollapse>
</Card> </Flex>
</InfoCollapse> </div>
</Flex>
<InfoCollapse </>
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': documentSizeId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
)}
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -1,3 +1,4 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd' import { Space, Flex, Card } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
@ -25,6 +26,8 @@ log.setLevel(config.logLevel)
const FilamentInfo = () => { const FilamentInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const filamentId = new URLSearchParams(location.search).get('filamentId') const filamentId = new URLSearchParams(location.search).get('filamentId')
const [collapseState, updateCollapseState] = useCollapseState( const [collapseState, updateCollapseState] = useCollapseState(
'FilamentInfo', 'FilamentInfo',
@ -36,104 +39,110 @@ const FilamentInfo = () => {
} }
) )
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
return true
}
}
return ( return (
<EditObjectForm id={filamentId} type='filament' style={{ height: '100%' }}> <>
{({ <Flex
loading, gap='large'
isEditing, vertical='true'
startEditing, style={{
cancelEditing, height: 'calc(var(--unit-100vh) - 155px)',
handleUpdate, minHeight: 0
formValid, }}
objectData, >
editLoading, <Flex justify={'space-between'}>
lock, <Space size='middle'>
fetchObject <Space size='small'>
}) => { <ObjectActions
// Define actions for ActionHandler type='filament'
const actions = { id={filamentId}
reload: () => { disabled={editFormState.loading}
fetchObject() />
return true <ViewButton
}, disabled={editFormState.loading}
edit: () => { items={[
startEditing() { key: 'info', label: 'Filament Information' },
return false { key: 'stocks', label: 'Filament Stocks' },
}, { key: 'notes', label: 'Notes' },
cancelEdit: () => { { key: 'auditLogs', label: 'Audit Logs' }
cancelEditing() ]}
return true visibleState={collapseState}
}, updateVisibleState={updateCollapseState}
finishEdit: () => { />
handleUpdate() </Space>
return true <LockIndicator lock={editFormState.lock} />
} </Space>
} <Space>
<EditButtons
isEditing={editFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
/>
</Space>
</Flex>
return ( <div style={{ height: '100%', overflowY: 'scroll' }}>
<ActionHandler actions={actions} loading={loading}> <Flex vertical gap={'large'}>
{({ callAction }) => ( <ActionHandler
<Flex actions={actions}
gap='large' loading={editFormState.loading}
vertical='true' ref={actionHandlerRef}
style={{ >
height: 'calc(var(--unit-100vh) - 155px)', <InfoCollapse
minHeight: 0 title='Filament Information'
}} icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={filamentId}
<Space size='small'> type='filament'
<ObjectActions style={{ height: '100%' }}
type='filament' ref={editFormRef}
id={filamentId} onStateChange={(state) => {
disabled={loading} setEditFormState((prev) => ({ ...prev, ...state }))
/> }}
<ViewButton >
disabled={loading} {({ loading, isEditing, objectData }) => {
items={[ return (
{ key: 'info', label: 'Filament Information' },
{ key: 'stocks', label: 'Filament Stocks' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing}
handleUpdate={() => {
callAction('finishEdit')
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={lock?.locked || loading}
loading={editLoading}
/>
</Space>
</Flex>
<div style={{ height: '100%', overflowY: 'scroll' }}>
<Flex vertical gap={'large'}>
<InfoCollapse
title='Filament Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo <ObjectInfo
loading={loading} loading={loading}
indicator={<LoadingOutlined />} indicator={<LoadingOutlined />}
@ -141,73 +150,69 @@ const FilamentInfo = () => {
type='filament' type='filament'
objectData={objectData} objectData={objectData}
/> />
</InfoCollapse> )
}}
</EditObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse <InfoCollapse
title='Filament Stocks' title='Filament Stocks'
icon={<FilamentIcon />} icon={<FilamentIcon />}
active={collapseState.stocks} active={collapseState.stocks}
onToggle={(expanded) => onToggle={(expanded) => updateCollapseState('stocks', expanded)}
updateCollapseState('stocks', expanded) collapseKey='stocks'
} >
collapseKey='stocks' {editFormState.loading ? (
> <InfoCollapsePlaceholder />
{loading ? ( ) : (
<InfoCollapsePlaceholder /> <ObjectTable
) : ( type='filamentStock'
<ObjectTable masterFilter={{ 'filament._id': filamentId }}
type='filamentStock' visibleColumns={{
masterFilter={{ 'filament._id': filamentId }} filament: false,
visibleColumns={{ 'filament._id': false,
filament: false, startingWeight: false
'filament._id': false, }}
startingWeight: false />
}} )}
/> </InfoCollapse>
)}
</InfoCollapse>
<InfoCollapse <InfoCollapse
title='Notes' title='Notes'
icon={<NoteIcon />} icon={<NoteIcon />}
active={collapseState.notes} active={collapseState.notes}
onToggle={(expanded) => onToggle={(expanded) => updateCollapseState('notes', expanded)}
updateCollapseState('notes', expanded) collapseKey='notes'
} >
collapseKey='notes' <Card>
> <NotesPanel _id={filamentId} type='filament' />
<Card> </Card>
<NotesPanel _id={filamentId} type='filament' /> </InfoCollapse>
</Card>
</InfoCollapse>
<InfoCollapse <InfoCollapse
title='Audit Logs' title='Audit Logs'
icon={<AuditLogIcon />} icon={<AuditLogIcon />}
active={collapseState.auditLogs} active={collapseState.auditLogs}
onToggle={(expanded) => onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded) updateCollapseState('auditLogs', expanded)
} }
collapseKey='auditLogs' collapseKey='auditLogs'
> >
{loading ? ( {editFormState.loading ? (
<InfoCollapsePlaceholder /> <InfoCollapsePlaceholder />
) : ( ) : (
<ObjectTable <ObjectTable
type='auditLog' type='auditLog'
masterFilter={{ 'parent._id': filamentId }} masterFilter={{ 'parent._id': filamentId }}
visibleColumns={{ _id: false, 'parent._id': false }} visibleColumns={{ _id: false, 'parent._id': false }}
/> />
)} )}
</InfoCollapse> </InfoCollapse>
</Flex> </Flex>
</div> </div>
</Flex> </Flex>
)} </>
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -1,3 +1,4 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex } from 'antd' import { Space, Flex } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
@ -17,6 +18,8 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
const NoteTypeInfo = () => { const NoteTypeInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const noteTypeId = new URLSearchParams(location.search).get('noteTypeId') const noteTypeId = new URLSearchParams(location.search).get('noteTypeId')
const [collapseState, updateCollapseState] = useCollapseState( const [collapseState, updateCollapseState] = useCollapseState(
'NoteTypeInfo', 'NoteTypeInfo',
@ -25,140 +28,137 @@ const NoteTypeInfo = () => {
auditLogs: true auditLogs: true
} }
) )
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
return true
}
}
return ( return (
<EditObjectForm <>
id={noteTypeId} <Flex
type='noteType' gap='large'
style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }} vertical='true'
> style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }}
{({ >
loading, <Flex justify={'space-between'}>
isEditing, <Space size='middle'>
startEditing, <Space size='small'>
cancelEditing, <ObjectActions
handleUpdate, type='noteType'
formValid, id={noteTypeId}
objectData, disabled={editFormState.loading}
editLoading, />
lock, <ViewButton
fetchObject disabled={editFormState.loading}
}) => { items={[
// Define actions for ActionHandler { key: 'info', label: 'Note Type Information' },
const actions = { { key: 'auditLogs', label: 'Audit Logs' }
reload: () => { ]}
fetchObject() visibleState={collapseState}
return true updateVisibleState={updateCollapseState}
}, />
edit: () => { </Space>
startEditing() <LockIndicator lock={editFormState.lock} />
return false </Space>
}, <Space>
cancelEdit: () => { <EditButtons
cancelEditing() isEditing={editFormState.isEditing}
return true handleUpdate={() => {
}, actionHandlerRef.current.callAction('finishEdit')
finishEdit: () => { }}
handleUpdate() cancelEditing={() => {
return true actionHandlerRef.current.callAction('cancelEdit')
} }}
} startEditing={() => {
actionHandlerRef.current.callAction('edit')
return ( }}
<ActionHandler actions={actions} loading={loading}> editLoading={editFormState.editLoading}
{({ callAction }) => ( formValid={editFormState.formValid}
<Flex disabled={editFormState.lock?.locked || editFormState.loading}
gap='large' loading={editFormState.editLoading}
vertical='true' />
style={{ height: '100%', minHeight: 0 }} </Space>
</Flex>
<div style={{ height: '100%', overflow: 'auto' }}>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Note Type Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={noteTypeId}
<Space size='small'> type='noteType'
<ObjectActions style={{ height: '100%' }}
type='noteType' ref={editFormRef}
id={noteTypeId} onStateChange={(state) => {
disabled={loading} setEditFormState((prev) => ({ ...prev, ...state }))
/> }}
<ViewButton >
disabled={loading} {({ loading, isEditing, objectData }) => (
items={[ <ObjectInfo
{ key: 'info', label: 'Note Type Information' }, loading={loading}
{ key: 'auditLogs', label: 'Audit Logs' } indicator={<LoadingOutlined />}
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing} isEditing={isEditing}
handleUpdate={() => { type='noteType'
callAction('finishEdit') objectData={objectData}
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={lock?.locked || loading}
loading={editLoading}
/> />
</Space> )}
</Flex> </EditObjectForm>
</InfoCollapse>
<div style={{ height: '100%', overflow: 'auto' }}> </ActionHandler>
<Flex vertical gap={'large'}> <InfoCollapse
<InfoCollapse title='Audit Logs'
title='Note Type Information' icon={<AuditLogIcon />}
icon={<InfoCircleIcon />} active={collapseState.auditLogs}
active={collapseState.info} onToggle={(expanded) =>
onToggle={(expanded) => updateCollapseState('auditLogs', expanded)
updateCollapseState('info', expanded) }
} collapseKey='auditLogs'
collapseKey='info' >
> {editFormState.loading ? (
<ObjectInfo <InfoCollapsePlaceholder />
loading={loading} ) : (
indicator={<LoadingOutlined />} <ObjectTable
isEditing={isEditing} type='auditLog'
type='noteType' masterFilter={{ 'parent._id': noteTypeId }}
objectData={objectData} visibleColumns={{ _id: false, 'parent._id': false }}
/> />
</InfoCollapse> )}
</InfoCollapse>
<InfoCollapse </Flex>
title='Audit Logs' </div>
icon={<AuditLogIcon />} </Flex>
active={collapseState.auditLogs} </>
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': noteTypeId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
)}
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -1,4 +1,4 @@
import { useContext } from 'react' import { useRef, useState, useContext } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd' import { Space, Flex, Card } from 'antd'
import useCollapseState from '../../hooks/useCollapseState' import useCollapseState from '../../hooks/useCollapseState'
@ -20,168 +20,165 @@ import { ApiServerContext } from '../../context/ApiServerContext'
const PartInfo = () => { const PartInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const partId = new URLSearchParams(location.search).get('partId') const partId = new URLSearchParams(location.search).get('partId')
const { fetchObjectContent } = useContext(ApiServerContext) const { fetchObjectContent } = useContext(ApiServerContext)
const [collapseState, updateCollapseState] = useCollapseState('PartInfo', { const [collapseState, updateCollapseState] = useCollapseState('PartInfo', {
info: true, info: true,
parts: true, parts: true,
notes: true, notes: true,
auditLogs: true auditLogs: true
}) })
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
return true
},
download: () => {
if (partId && editFormRef?.current?.getObjectData) {
const objectData = editFormRef.current.getObjectData()
fetchObjectContent(partId, 'part', `${objectData?.name || 'part'}.stl`)
return true
}
}
}
return ( return (
<EditObjectForm <>
id={partId} <Flex
type='part' gap='large'
style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }} vertical='true'
> style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }}
{({ >
loading, <Flex justify={'space-between'}>
isEditing, <Space size='middle'>
startEditing, <Space size='small'>
cancelEditing, <ObjectActions
handleUpdate, type='part'
formValid, id={partId}
objectData, disabled={editFormState.loading}
editLoading, />
lock, <ViewButton
fetchObject disabled={editFormState.loading}
}) => { items={[
const actions = { { key: 'info', label: 'Part Information' },
reload: () => { { key: 'notes', label: 'Notes' },
fetchObject() { key: 'auditLogs', label: 'Audit Logs' }
return true ]}
}, visibleState={collapseState}
edit: () => { updateVisibleState={updateCollapseState}
startEditing() />
return false </Space>
}, <LockIndicator lock={editFormState.lock} />
cancelEdit: () => { </Space>
cancelEditing() <Space>
return true <EditButtons
}, isEditing={editFormState.isEditing}
finishEdit: () => { handleUpdate={() => {
handleUpdate() actionHandlerRef.current.callAction('finishEdit')
return true }}
}, cancelEditing={() => {
download: () => { actionHandlerRef.current.callAction('cancelEdit')
if (partId) { }}
fetchObjectContent(partId, 'part', `${objectData.name}.stl`) startEditing={() => {
return true actionHandlerRef.current.callAction('edit')
} }}
} editLoading={editFormState.editLoading}
} formValid={editFormState.formValid}
return ( disabled={editFormState.lock?.locked || editFormState.loading}
<ActionHandler actions={actions} loading={loading}> loading={editFormState.editLoading}
{({ callAction }) => ( />
<Flex </Space>
gap='large' </Flex>
vertical='true' <div style={{ height: '100%', overflow: 'auto' }}>
style={{ height: '100%', minHeight: 0 }} <Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Part Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={partId}
<Space size='small'> type='part'
<ObjectActions style={{ height: '100%' }}
type='part' ref={editFormRef}
id={partId} onStateChange={(state) => {
disabled={loading} setEditFormState((prev) => ({ ...prev, ...state }))
/> }}
<ViewButton >
disabled={loading} {({ loading, isEditing, objectData }) => (
items={[ <ObjectInfo
{ key: 'info', label: 'Part Information' }, loading={loading}
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing} isEditing={isEditing}
handleUpdate={() => { type='part'
callAction('finishEdit') objectData={objectData}
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={lock?.locked || loading}
loading={editLoading}
/> />
</Space> )}
</Flex> </EditObjectForm>
</InfoCollapse>
<div style={{ height: '100%', overflow: 'auto' }}> </ActionHandler>
<Flex vertical gap={'large'}> <InfoCollapse
<InfoCollapse title='Notes'
title='Part Information' icon={<NoteIcon />}
icon={<InfoCircleIcon />} active={collapseState.notes}
active={collapseState.info} onToggle={(expanded) => updateCollapseState('notes', expanded)}
onToggle={(expanded) => collapseKey='notes'
updateCollapseState('info', expanded) >
} <Card>
collapseKey='info' <NotesPanel _id={partId} type='part' />
> </Card>
<ObjectInfo </InfoCollapse>
loading={loading} <InfoCollapse
isEditing={isEditing} title='Audit Logs'
type='part' icon={<AuditLogIcon />}
objectData={objectData} active={collapseState.auditLogs}
/> onToggle={(expanded) =>
</InfoCollapse> updateCollapseState('auditLogs', expanded)
}
<InfoCollapse collapseKey='auditLogs'
title='Notes' >
icon={<NoteIcon />} {editFormState.loading ? (
active={collapseState.notes} <InfoCollapsePlaceholder />
onToggle={(expanded) => ) : (
updateCollapseState('notes', expanded) <ObjectTable
} type='auditLog'
collapseKey='notes' masterFilter={{ 'parent._id': partId }}
> visibleColumns={{ _id: false, 'parent._id': false }}
<Card> />
<NotesPanel _id={partId} type='part' /> )}
</Card> </InfoCollapse>
</InfoCollapse> </Flex>
</div>
<InfoCollapse </Flex>
title='Audit Logs' </>
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': partId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
)}
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -1,3 +1,4 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd' import { Space, Flex, Card } from 'antd'
import useCollapseState from '../../hooks/useCollapseState' import useCollapseState from '../../hooks/useCollapseState'
@ -19,6 +20,8 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
const ProductInfo = () => { const ProductInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const productId = new URLSearchParams(location.search).get('productId') const productId = new URLSearchParams(location.search).get('productId')
const [collapseState, updateCollapseState] = useCollapseState('ProductInfo', { const [collapseState, updateCollapseState] = useCollapseState('ProductInfo', {
info: true, info: true,
@ -26,172 +29,165 @@ const ProductInfo = () => {
notes: true, notes: true,
auditLogs: true auditLogs: true
}) })
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
return true
}
}
return ( return (
<EditObjectForm <>
id={productId} <Flex
type='product' gap='large'
style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }} vertical='true'
> style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }}
{({ >
loading, <Flex justify={'space-between'}>
isEditing, <Space size='middle'>
startEditing, <Space size='small'>
cancelEditing, <ObjectActions
handleUpdate, type='product'
formValid, id={productId}
objectData, disabled={editFormState.loading}
editLoading, />
lock, <ViewButton
fetchObject disabled={editFormState.loading}
}) => { items={[
const actions = { { key: 'info', label: 'Product Information' },
reload: () => { { key: 'parts', label: 'Product Parts' },
fetchObject() { key: 'notes', label: 'Notes' },
return true { key: 'auditLogs', label: 'Audit Logs' }
}, ]}
edit: () => { visibleState={collapseState}
startEditing() updateVisibleState={updateCollapseState}
return false />
}, </Space>
cancelEdit: () => { <LockIndicator lock={editFormState.lock} />
cancelEditing() </Space>
return true <Space>
}, <EditButtons
finishEdit: () => { isEditing={editFormState.isEditing}
handleUpdate() handleUpdate={() => {
return true actionHandlerRef.current.callAction('finishEdit')
} }}
} cancelEditing={() => {
return ( actionHandlerRef.current.callAction('cancelEdit')
<ActionHandler actions={actions} loading={loading}> }}
{({ callAction }) => ( startEditing={() => {
<Flex actionHandlerRef.current.callAction('edit')
gap='large' }}
vertical='true' editLoading={editFormState.editLoading}
style={{ height: '100%', minHeight: 0 }} formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
/>
</Space>
</Flex>
<div style={{ height: '100%', overflow: 'auto' }}>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Product Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={productId}
<Space size='small'> type='product'
<ObjectActions style={{ height: '100%' }}
type='product' ref={editFormRef}
id={productId} onStateChange={(state) => {
disabled={loading} setEditFormState((prev) => ({ ...prev, ...state }))
/> }}
<ViewButton >
disabled={loading} {({ loading, isEditing, objectData }) => (
items={[ <ObjectInfo
{ key: 'info', label: 'Product Information' }, loading={loading}
{ key: 'parts', label: 'Product Parts' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing} isEditing={isEditing}
handleUpdate={() => { type='product'
callAction('finishEdit') objectData={objectData}
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={lock?.locked || loading}
loading={editLoading}
/> />
</Space> )}
</Flex> </EditObjectForm>
</InfoCollapse>
<div style={{ height: '100%', overflow: 'auto' }}> </ActionHandler>
<Flex vertical gap={'large'}> <InfoCollapse
<InfoCollapse title='Product Parts'
title='Product Information' icon={<ProductIcon />}
icon={<InfoCircleIcon />} active={collapseState.parts}
active={collapseState.info} onToggle={(expanded) => updateCollapseState('parts', expanded)}
onToggle={(expanded) => collapseKey='parts'
updateCollapseState('info', expanded) >
} <ObjectTable
collapseKey='info' type='part'
> visibleColumns={{
<ObjectInfo product: false,
loading={loading} 'product._id': false
isEditing={isEditing} }}
type='product' masterFilter={{ 'product._id': productId }}
objectData={objectData} />
/> </InfoCollapse>
</InfoCollapse> <InfoCollapse
title='Notes'
<InfoCollapse icon={<NoteIcon />}
title='Product Parts' active={collapseState.notes}
icon={<ProductIcon />} onToggle={(expanded) => updateCollapseState('notes', expanded)}
active={collapseState.parts} collapseKey='notes'
onToggle={(expanded) => >
updateCollapseState('parts', expanded) <Card>
} <NotesPanel _id={productId} type='product' />
collapseKey='parts' </Card>
> </InfoCollapse>
<ObjectTable <InfoCollapse
type='part' title='Audit Logs'
visibleColumns={{ icon={<AuditLogIcon />}
product: false, active={collapseState.auditLogs}
'product._id': false onToggle={(expanded) =>
}} updateCollapseState('auditLogs', expanded)
masterFilter={{ 'product._id': productId }} }
/> collapseKey='auditLogs'
</InfoCollapse> >
{editFormState.loading ? (
<InfoCollapse <InfoCollapsePlaceholder />
title='Notes' ) : (
icon={<NoteIcon />} <ObjectTable
active={collapseState.notes} type='auditLog'
onToggle={(expanded) => masterFilter={{ 'parent._id': productId }}
updateCollapseState('notes', expanded) visibleColumns={{ _id: false, 'parent._id': false }}
} />
collapseKey='notes' )}
> </InfoCollapse>
<Card> </Flex>
<NotesPanel _id={productId} type='product' /> </div>
</Card> </Flex>
</InfoCollapse> </>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': productId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
)}
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -1,3 +1,4 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd' import { Space, Flex, Card } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
@ -19,161 +20,157 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
const UserInfo = () => { const UserInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const userId = new URLSearchParams(location.search).get('userId') const userId = new URLSearchParams(location.search).get('userId')
const [collapseState, updateCollapseState] = useCollapseState('UserInfo', { const [collapseState, updateCollapseState] = useCollapseState('UserInfo', {
info: true, info: true,
notes: true, notes: true,
auditLogs: true auditLogs: true
}) })
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
return true
}
}
return ( return (
<EditObjectForm <>
id={userId} <Flex
type='user' gap='large'
style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }} vertical='true'
> style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }}
{({ >
loading, <Flex justify={'space-between'}>
isEditing, <Space size='middle'>
startEditing, <Space size='small'>
cancelEditing, <ObjectActions
handleUpdate, type='user'
formValid, id={userId}
objectData, disabled={editFormState.loading}
editLoading, />
lock, <ViewButton
fetchObject disabled={editFormState.loading}
}) => { items={[
// Define actions for ActionHandler { key: 'info', label: 'User Information' },
const actions = { { key: 'notes', label: 'Notes' },
reload: () => { { key: 'auditLogs', label: 'Audit Logs' }
fetchObject() ]}
return true visibleState={collapseState}
}, updateVisibleState={updateCollapseState}
edit: () => { />
startEditing() </Space>
return false <LockIndicator lock={editFormState.lock} />
}, </Space>
cancelEdit: () => { <Space>
cancelEditing() <EditButtons
return true isEditing={editFormState.isEditing}
}, handleUpdate={() => {
finishEdit: () => { actionHandlerRef.current.callAction('finishEdit')
handleUpdate() }}
return true cancelEditing={() => {
} actionHandlerRef.current.callAction('cancelEdit')
} }}
startEditing={() => {
return ( actionHandlerRef.current.callAction('edit')
<ActionHandler actions={actions} loading={loading}> }}
{({ callAction }) => ( editLoading={editFormState.editLoading}
<Flex formValid={editFormState.formValid}
gap='large' disabled={editFormState.lock?.locked || editFormState.loading}
vertical='true' loading={editFormState.editLoading}
style={{ height: '100%', minHeight: 0 }} />
</Space>
</Flex>
<div style={{ height: '100%', overflow: 'auto' }}>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={editFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='User Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={userId}
<Space size='small'> type='user'
<ObjectActions style={{ height: '100%' }}
type='user' ref={editFormRef}
id={userId} onStateChange={(state) => {
disabled={loading} setEditFormState((prev) => ({ ...prev, ...state }))
/> }}
<ViewButton >
disabled={loading} {({ loading, isEditing, objectData }) => (
items={[ <ObjectInfo
{ key: 'info', label: 'User Information' }, loading={loading}
{ key: 'notes', label: 'Notes' }, indicator={<LoadingOutlined />}
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing} isEditing={isEditing}
handleUpdate={() => { type='user'
callAction('finishEdit') objectData={objectData}
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={lock?.locked || loading || true}
loading={editLoading}
/> />
</Space> )}
</Flex> </EditObjectForm>
</InfoCollapse>
<div style={{ height: '100%', overflow: 'auto' }}> </ActionHandler>
<Flex vertical gap={'large'}> <InfoCollapse
<InfoCollapse title='Notes'
title='User Information' icon={<NoteIcon />}
icon={<InfoCircleIcon />} active={collapseState.notes}
active={collapseState.info} onToggle={(expanded) => updateCollapseState('notes', expanded)}
onToggle={(expanded) => collapseKey='notes'
updateCollapseState('info', expanded) >
} <Card>
collapseKey='info' <NotesPanel _id={userId} type='user' />
> </Card>
<ObjectInfo </InfoCollapse>
loading={loading} <InfoCollapse
indicator={<LoadingOutlined />} title='Audit Logs'
isEditing={isEditing} icon={<AuditLogIcon />}
type='user' active={collapseState.auditLogs}
objectData={objectData} onToggle={(expanded) =>
/> updateCollapseState('auditLogs', expanded)
</InfoCollapse> }
collapseKey='auditLogs'
<InfoCollapse >
title='Notes' {editFormState.loading ? (
icon={<NoteIcon />} <InfoCollapsePlaceholder />
active={collapseState.notes} ) : (
onToggle={(expanded) => <ObjectTable
updateCollapseState('notes', expanded) type='auditLog'
} masterFilter={{ 'parent._id': userId }}
collapseKey='notes' visibleColumns={{ _id: false, 'parent._id': false }}
> />
<Card> )}
<NotesPanel _id={userId} type='user' /> </InfoCollapse>
</Card> </Flex>
</InfoCollapse> </div>
</Flex>
<InfoCollapse </>
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': userId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
)}
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -1,3 +1,4 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd' import { Space, Flex, Card } from 'antd'
import loglevel from 'loglevel' import loglevel from 'loglevel'
@ -23,165 +24,160 @@ log.setLevel(config.logLevel)
const VendorInfo = () => { const VendorInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const vendorId = new URLSearchParams(location.search).get('vendorId') const vendorId = new URLSearchParams(location.search).get('vendorId')
const [collapseState, updateCollapseState] = useCollapseState('VendorInfo', { const [collapseState, updateCollapseState] = useCollapseState('VendorInfo', {
info: true, info: true,
notes: true, notes: true,
auditLogs: true auditLogs: true
}) })
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
editFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
editFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
editFormRef?.current?.handleUpdate?.()
return true
},
delete: () => {
editFormRef?.current?.handleDelete?.()
return true
}
}
return ( return (
<EditObjectForm <>
id={vendorId} <Flex
type='vendor' gap='large'
style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }} vertical='true'
> style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }}
{({ >
loading, <Flex justify={'space-between'}>
isEditing, <Space size='middle'>
startEditing, <Space size='small'>
cancelEditing, <ObjectActions
handleFetchObject, type='vendor'
handleUpdate, id={vendorId}
handleDelete, disabled={editFormState.loading}
formValid, />
objectData, <ViewButton
editLoading, disabled={editFormState.loading}
lock items={[
}) => { { key: 'info', label: 'Vendor Information' },
// Define actions for ActionHandler { key: 'notes', label: 'Notes' },
const actions = { { key: 'auditLogs', label: 'Audit Logs' }
reload: () => { ]}
handleFetchObject() visibleState={collapseState}
return true updateVisibleState={updateCollapseState}
}, />
edit: () => { </Space>
startEditing() <LockIndicator lock={editFormState.lock} />
return false </Space>
}, <Space>
cancelEdit: () => { <EditButtons
cancelEditing() isEditing={editFormState.isEditing}
return true handleUpdate={() => {
}, actionHandlerRef.current.callAction('finishEdit')
finishEdit: () => { }}
handleUpdate() cancelEditing={() => {
return true actionHandlerRef.current.callAction('cancelEdit')
}, }}
delete: () => { startEditing={() => {
handleDelete() actionHandlerRef.current.callAction('edit')
return true }}
} editLoading={editFormState.editLoading}
} formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
return ( loading={editFormState.editLoading}
<ActionHandler actions={actions} loading={loading}> />
{({ callAction }) => ( </Space>
<Flex </Flex>
gap='large' <div style={{ height: '100%', overflow: 'auto' }}>
vertical='true' <Flex vertical gap={'large'}>
style={{ height: '100%', minHeight: 0 }} <ActionHandler
actions={actions}
loading={editFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Vendor Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={vendorId}
<Space size='small'> type='vendor'
<ObjectActions style={{ height: '100%' }}
type='vendor' ref={editFormRef}
id={vendorId} onStateChange={(state) => {
disabled={loading} setEditFormState((prev) => ({ ...prev, ...state }))
/> }}
<ViewButton >
disabled={loading} {({ loading, isEditing, objectData }) => (
items={[ <ObjectInfo
{ key: 'info', label: 'Vendor Information' }, loading={loading}
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing} isEditing={isEditing}
handleUpdate={() => { type='vendor'
callAction('finishEdit') objectData={objectData}
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={lock?.locked || loading}
loading={editLoading}
/> />
</Space> )}
</Flex> </EditObjectForm>
</InfoCollapse>
<div style={{ height: '100%', overflow: 'auto' }}> </ActionHandler>
<Flex vertical gap={'large'}> <InfoCollapse
<InfoCollapse title='Notes'
title='Vendor Information' icon={<NoteIcon />}
icon={<InfoCircleIcon />} active={collapseState.notes}
active={collapseState.info} onToggle={(expanded) => updateCollapseState('notes', expanded)}
onToggle={(expanded) => collapseKey='notes'
updateCollapseState('info', expanded) >
} <Card>
collapseKey='info' <NotesPanel _id={vendorId} type='vendor' />
> </Card>
<ObjectInfo </InfoCollapse>
loading={loading} <InfoCollapse
isEditing={isEditing} title='Audit Logs'
type='vendor' icon={<AuditLogIcon />}
objectData={objectData} active={collapseState.auditLogs}
/> onToggle={(expanded) =>
</InfoCollapse> updateCollapseState('auditLogs', expanded)
}
<InfoCollapse collapseKey='auditLogs'
title='Notes' >
icon={<NoteIcon />} {editFormState.loading ? (
active={collapseState.notes} <InfoCollapsePlaceholder />
onToggle={(expanded) => ) : (
updateCollapseState('notes', expanded) <ObjectTable
} type='auditLog'
collapseKey='notes' masterFilter={{ 'parent._id': vendorId }}
> visibleColumns={{ _id: false, 'parent._id': false }}
<Card> />
<NotesPanel _id={vendorId} type='vendor' /> )}
</Card> </InfoCollapse>
</InfoCollapse> </Flex>
</div>
<InfoCollapse </Flex>
title='Audit Logs' </>
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': vendorId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
)}
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -1,223 +1,225 @@
import { useContext } from 'react' import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card, Typography } from 'antd' import { Space, Flex, Card, Typography } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import useCollapseState from '../../hooks/useCollapseState' import loglevel from 'loglevel'
import NotesPanel from '../../common/NotesPanel' import config from '../../../../config.js'
import InfoCollapse from '../../common/InfoCollapse' import useCollapseState from '../../hooks/useCollapseState.js'
import ObjectInfo from '../../common/ObjectInfo' import NotesPanel from '../../common/NotesPanel.jsx'
import ViewButton from '../../common/ViewButton' import InfoCollapse from '../../common/InfoCollapse.jsx'
import EditObjectForm from '../../common/EditObjectForm' import ObjectInfo from '../../common/ObjectInfo.jsx'
import EditButtons from '../../common/EditButtons' import ViewButton from '../../common/ViewButton.jsx'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx' import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx' import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx' import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon.jsx' import EditObjectForm from '../../common/EditObjectForm.jsx'
import { ApiServerContext } from '../../context/ApiServerContext' import EditButtons from '../../common/EditButtons.jsx'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import EyeIcon from '../../../Icons/EyeIcon.jsx'
const { Text } = Typography const { Text } = Typography
const log = loglevel.getLogger('GCodeFileInfo')
log.setLevel(config.logLevel)
const GCodeFileInfo = () => { const GCodeFileInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId') const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId')
const { fetchObjectContent } = useContext(ApiServerContext)
const [collapseState, updateCollapseState] = useCollapseState( const [collapseState, updateCollapseState] = useCollapseState(
'gcodeFileInfo', 'GCodeFileInfo',
{ {
info: true, info: true,
preview: true, stocks: true,
notes: true, notes: true,
auditLogs: true auditLogs: true
} }
) )
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
locked: false,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current.handleFetchObject()
return true
},
edit: () => {
editFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
editFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
editFormRef?.current.handleUpdate()
return true
}
}
return ( return (
<EditObjectForm <>
id={gcodeFileId} <Flex
type='gcodefile' gap='large'
style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }} vertical='true'
> style={{
{({ height: 'calc(var(--unit-100vh) - 155px)',
loading, minHeight: 0
isEditing, }}
startEditing, >
cancelEditing, <Flex justify={'space-between'}>
handleUpdate, <Space size='middle'>
formValid, <Space size='small'>
objectData, <ObjectActions
editLoading, type='gcodeFile'
lock, id={gcodeFileId}
fetchObject disabled={editFormState.loading}
}) => { />
// Define actions that can be triggered via URL, now with access to startEditing <ViewButton
const actions = { disabled={editFormState.loading}
reload: () => { items={[
fetchObject() { key: 'info', label: 'GCode File Information' },
return true { key: 'preview', label: 'GCode File Preview' },
}, { key: 'notes', label: 'Notes' },
edit: () => { { key: 'auditLogs', label: 'Audit Logs' }
startEditing() ]}
return false visibleState={collapseState}
}, updateVisibleState={updateCollapseState}
cancelEdit: () => { />
cancelEditing() </Space>
return true <LockIndicator lock={editFormState.lock} />
}, </Space>
finishEdit: () => { <Space>
handleUpdate() <EditButtons
return true isEditing={editFormState.isEditing}
}, handleUpdate={() => {
download: () => { actionHandlerRef.current.callAction('finishEdit')
if (gcodeFileId) { }}
fetchObjectContent( cancelEditing={() => {
gcodeFileId, actionHandlerRef.current.callAction('cancelEdit')
'gcodeFile', }}
`${objectData.name}.gcode` startEditing={() => {
) actionHandlerRef.current.callAction('edit')
return true }}
} editLoading={editFormState.editLoading}
} formValid={editFormState.formValid}
} disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
/>
</Space>
</Flex>
return ( <div style={{ height: '100%', overflowY: 'scroll' }}>
<ActionHandler actions={actions} loading={loading}> <Flex vertical gap={'large'}>
{({ callAction }) => ( <ActionHandler
<Flex actions={actions}
gap='large' loading={editFormState.loading}
vertical='true' ref={actionHandlerRef}
style={{ height: '100%', minHeight: 0 }} >
<EditObjectForm
id={gcodeFileId}
type='gcodeFile'
style={{ height: '100%' }}
ref={editFormRef}
onStateChange={(state) => {
console.log('Got edit form state change', state)
setEditFormState((prev) => ({ ...prev, ...state }))
}}
> >
<Flex justify={'space-between'}> {({ loading, isEditing, objectData }) => {
<Space size='middle'> return (
<Space size='small'> <Flex vertical gap={'large'}>
<ObjectActions <InfoCollapse
type='gcodeFile' title='GCode File Information'
id={gcodeFileId} icon={<InfoCircleIcon />}
disabled={loading} active={collapseState.info}
/> onToggle={(expanded) =>
<ViewButton updateCollapseState('info', expanded)
disabled={loading} }
items={[ collapseKey='info'
{ key: 'info', label: 'GCode File Information' }, >
{ key: 'preview', label: 'GCode File Preview' }, <ObjectInfo
{ key: 'notes', label: 'Notes' }, loading={loading}
{ key: 'auditLogs', label: 'Audit Logs' } indicator={<LoadingOutlined />}
]} isEditing={isEditing}
visibleState={collapseState} type='gcodeFile'
updateVisibleState={updateCollapseState} objectData={objectData}
/> visibleProperties={{}}
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing}
handleUpdate={() => {
callAction('finishEdit')
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={lock?.locked || loading}
loading={editLoading}
/>
</Space>
</Flex>
<div style={{ height: '100%', overflow: 'auto' }}>
<Flex vertical gap={'large'}>
<InfoCollapse
title='GCode File Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
objectData={objectData}
type='gcodeFile'
/>
</InfoCollapse>
<InfoCollapse
title='GCode File Preview'
icon={<GCodeFileIcon />}
active={collapseState.preview}
onToggle={(expanded) =>
updateCollapseState('preview', expanded)
}
collapseKey='preview'
>
<Card>
{objectData?.gcodeFileInfo?.thumbnail ? (
<img
src={`data:image/png;base64,${objectData.gcodeFileInfo.thumbnail.data}`}
alt='GCodeFile'
style={{ maxWidth: '100%' }}
/>
) : (
<Text>n/a</Text>
)}
</Card>
</InfoCollapse>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) =>
updateCollapseState('notes', expanded)
}
collapseKey='notes'
>
<Card>
<NotesPanel _id={gcodeFileId} type='gcodeFile' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': gcodeFileId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/> />
)} </InfoCollapse>
</InfoCollapse> <InfoCollapse
</Flex> title='GCode File Preview'
</div> icon={<EyeIcon />}
</Flex> active={collapseState.preview}
)} onToggle={(expanded) =>
</ActionHandler> updateCollapseState('preview', expanded)
) }
}} collapseKey='preview'
</EditObjectForm> >
<Card>
{objectData?.gcodeFileInfo?.thumbnail ? (
<img
src={`data:image/png;base64,${objectData.gcodeFileInfo.thumbnail.data}`}
alt='GCodeFile'
style={{ maxWidth: '100%' }}
/>
) : (
<Text>n/a</Text>
)}
</Card>
</InfoCollapse>
</Flex>
)
}}
</EditObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={gcodeFileId} type='gcodeFile' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{editFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': gcodeFileId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
</>
) )
} }

View File

@ -1,25 +1,33 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd' import { Space, Flex, Card } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import useCollapseState from '../../hooks/useCollapseState' import loglevel from 'loglevel'
import NotesPanel from '../../common/NotesPanel' import config from '../../../../config.js'
import InfoCollapse from '../../common/InfoCollapse' import useCollapseState from '../../hooks/useCollapseState.js'
import ObjectInfo from '../../common/ObjectInfo' import NotesPanel from '../../common/NotesPanel.jsx'
import ViewButton from '../../common/ViewButton' import InfoCollapse from '../../common/InfoCollapse.jsx'
import EditObjectForm from '../../common/EditObjectForm' import ObjectInfo from '../../common/ObjectInfo.jsx'
import EditButtons from '../../common/EditButtons' import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import EditObjectForm from '../../common/EditObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
import LockIndicator from '../../common/LockIndicator.jsx' import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler' import ActionHandler from '../../common/ActionHandler.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon'
import JobIcon from '../../../Icons/JobIcon'
import AuditLogIcon from '../../../Icons/AuditLogIcon'
import NoteIcon from '../../../Icons/NoteIcon'
import ObjectActions from '../../common/ObjectActions.jsx' import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx' import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import JobIcon from '../../../Icons/JobIcon.jsx'
const log = loglevel.getLogger('JobInfo')
log.setLevel(config.logLevel)
const JobInfo = () => { const JobInfo = () => {
const location = useLocation() const location = useLocation()
const editFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const jobId = new URLSearchParams(location.search).get('jobId') const jobId = new URLSearchParams(location.search).get('jobId')
const [collapseState, updateCollapseState] = useCollapseState('JobInfo', { const [collapseState, updateCollapseState] = useCollapseState('JobInfo', {
info: true, info: true,
@ -28,152 +36,170 @@ const JobInfo = () => {
auditLogs: true auditLogs: true
}) })
const [editFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
locked: false,
loading: false
})
const actions = {
reload: () => {
editFormRef?.current.handleFetchObject()
return true
},
edit: () => {
editFormRef?.current.startEditing()
return false
},
cancelEdit: () => {
editFormRef?.current.cancelEditing()
return true
},
finishEdit: () => {
editFormRef?.current.handleUpdate()
return true
}
}
return ( return (
<EditObjectForm <>
id={jobId} <Flex
type='job' gap='large'
style={{ height: 'calc(var(--unit-100vh) - 155px)', minHeight: 0 }} vertical='true'
> style={{
{({ height: 'calc(var(--unit-100vh) - 155px)',
loading, minHeight: 0
isEditing, }}
formValid, >
objectData, <Flex justify={'space-between'}>
editLoading, <Space size='middle'>
lock, <Space size='small'>
fetchObject <ObjectActions
}) => { type='job'
// Define actions that can be triggered via URL, now with access to startEditing id={jobId}
const actions = { disabled={editFormState.loading}
reload: () => { />
fetchObject() <ViewButton
return true disabled={editFormState.loading}
} items={[
} { key: 'info', label: 'Job Information' },
{ key: 'subJobs', label: 'Sub Jobs' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={editFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={editFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={editFormState.editLoading}
formValid={editFormState.formValid}
disabled={editFormState.lock?.locked || editFormState.loading}
loading={editFormState.editLoading}
/>
</Space>
</Flex>
return ( <div style={{ height: '100%', overflowY: 'scroll' }}>
<ActionHandler actions={actions} loading={loading}> <Flex vertical gap={'large'}>
{({ callAction }) => ( <ActionHandler
<Flex actions={actions}
gap='large' loading={editFormState.loading}
vertical='true' ref={actionHandlerRef}
style={{ height: '100%', minHeight: 0 }} >
<InfoCollapse
title='Job Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
> >
<Flex justify={'space-between'}> <EditObjectForm
<Space size='middle'> id={jobId}
<Space size='small'> type='job'
<ObjectActions type='job' id={jobId} disabled={loading} /> style={{ height: '100%' }}
<ViewButton ref={editFormRef}
disabled={loading} onStateChange={(state) => {
items={[ setEditFormState((prev) => ({ ...prev, ...state }))
{ key: 'info', label: 'Job Information' }, }}
{ key: 'subJobs', label: 'Sub Jobs' }, >
{ key: 'notes', label: 'Notes' }, {({ loading, isEditing, objectData }) => (
{ key: 'auditLogs', label: 'Audit Logs' } <ObjectInfo
]} loading={loading}
visibleState={collapseState} indicator={<LoadingOutlined />}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={lock} />
</Space>
<Space>
<EditButtons
isEditing={isEditing} isEditing={isEditing}
handleUpdate={() => { type='job'
callAction('finishEdit') objectData={objectData}
}}
cancelEditing={() => {
callAction('cancelEdit')
}}
startEditing={() => {
callAction('edit')
}}
editLoading={editLoading}
formValid={formValid}
disabled={true}
loading={editLoading}
/> />
</Space> )}
</Flex> </EditObjectForm>
</InfoCollapse>
</ActionHandler>
<div style={{ height: '100%', overflow: 'auto' }}> <InfoCollapse
<Flex vertical gap={'large'}> title='Sub Jobs'
<InfoCollapse icon={<JobIcon />}
title='Job Information' active={collapseState.subJobs}
icon={<InfoCircleIcon />} onToggle={(expanded) => updateCollapseState('subJobs', expanded)}
active={collapseState.info} collapseKey='subJobs'
onToggle={(expanded) => >
updateCollapseState('info', expanded) <ObjectTable
} type='subJob'
collapseKey='info' masterFilter={{ 'job._id': jobId }}
> visibleColumns={{ 'job._id': false }}
<ObjectInfo />
loading={loading} </InfoCollapse>
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='job'
objectData={objectData}
/>
</InfoCollapse>
<InfoCollapse <InfoCollapse
title='Sub Jobs' title='Notes'
icon={<JobIcon />} icon={<NoteIcon />}
active={collapseState.subJobs} active={collapseState.notes}
onToggle={(expanded) => onToggle={(expanded) => updateCollapseState('notes', expanded)}
updateCollapseState('subJobs', expanded) collapseKey='notes'
} >
collapseKey='subJobs' <Card>
> <NotesPanel _id={jobId} type='job' />
<ObjectTable </Card>
type='subJob' </InfoCollapse>
masterFilter={{ 'job._id': jobId }}
visibleColumns={{ 'job._id': false }}
/>
</InfoCollapse>
<InfoCollapse <InfoCollapse
title='Notes' title='Audit Logs'
icon={<NoteIcon />} icon={<AuditLogIcon />}
active={collapseState.notes} active={collapseState.auditLogs}
onToggle={(expanded) => onToggle={(expanded) =>
updateCollapseState('notes', expanded) updateCollapseState('auditLogs', expanded)
} }
collapseKey='notes' collapseKey='auditLogs'
> >
<Card> {editFormState.loading ? (
<NotesPanel _id={jobId} type='job' /> <InfoCollapsePlaceholder />
</Card> ) : (
</InfoCollapse> <ObjectTable
type='auditLog'
<InfoCollapse masterFilter={{ 'parent._id': jobId }}
title='Audit Logs' visibleColumns={{ _id: false, 'parent._id': false }}
icon={<AuditLogIcon />} />
active={collapseState.auditLogs} )}
onToggle={(expanded) => </InfoCollapse>
updateCollapseState('auditLogs', expanded) </Flex>
} </div>
collapseKey='auditLogs' </Flex>
> </>
{loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': jobId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</div>
</Flex>
)}
</ActionHandler>
)
}}
</EditObjectForm>
) )
} }

View File

@ -83,7 +83,7 @@ const ActionHandler = forwardRef(
ActionHandler.displayName = 'ActionHandler' ActionHandler.displayName = 'ActionHandler'
ActionHandler.propTypes = { ActionHandler.propTypes = {
children: PropTypes.func, children: PropTypes.oneOf([PropTypes.func, PropTypes.object]),
actions: PropTypes.objectOf(PropTypes.func), actions: PropTypes.objectOf(PropTypes.func),
actionParam: PropTypes.string, actionParam: PropTypes.string,
clearAfterExecute: PropTypes.bool, clearAfterExecute: PropTypes.bool,

View File

@ -1,92 +0,0 @@
// NoteTypeSelect.js
import PropTypes from 'prop-types'
import { message, Tag, Select, Space } from 'antd'
import { useEffect, useState, useContext, useCallback } from 'react'
import axios from 'axios'
import { AuthContext } from '../context/AuthContext'
import config from '../../../config'
const NoteTypeSelect = ({ onChange, disabled, value = null }) => {
const [noteTypeOptions, setNoteTypeOptions] = useState([])
const [loading, setLoading] = useState(true)
const [messageApi] = message.useMessage()
const { authenticated } = useContext(AuthContext)
const fetchNoteTypesData = useCallback(async () => {
if (!authenticated) {
return
}
setLoading(true)
try {
const response = await axios.get(
`${config.backendUrl}/notetypes?active=true`,
{
headers: {
Accept: 'application/json'
},
withCredentials: true // Important for including cookies
}
)
const data = response.data
setNoteTypeOptions(() => {
var options = []
data.map((noteType) => {
var newNoteTypeOption = {
label: (
<Space>
<Tag color={noteType?.color}>{noteType.name}</Tag>
</Space>
),
value: noteType._id
}
options.push(newNoteTypeOption)
})
return options
})
setLoading(false)
} catch (error) {
if (error.response) {
// For other errors, show a message
messageApi.error(
'Error fetching note types data:',
error.response.status
)
} else {
messageApi.error(
'An unexpected error occurred. Please try again later.'
)
}
}
}, [authenticated, messageApi])
useEffect(() => {
if (authenticated) {
fetchNoteTypesData()
}
}, [authenticated, fetchNoteTypesData])
return (
<Select
options={noteTypeOptions}
onChange={onChange}
loading={loading}
disabled={disabled}
placeholder='Select note type'
style={{ width: '100%' }}
value={value}
/>
)
}
NoteTypeSelect.propTypes = {
onChange: PropTypes.func,
disabled: PropTypes.bool,
checkable: PropTypes.bool,
value: PropTypes.object
}
export default NoteTypeSelect

View File

@ -28,10 +28,10 @@ import config from '../../../config'
import { AuthContext } from '../context/AuthContext' import { AuthContext } from '../context/AuthContext'
import { ApiServerContext } from '../context/ApiServerContext' import { ApiServerContext } from '../context/ApiServerContext'
import InfoCircleIcon from '../../Icons/InfoCircleIcon' import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import NoteTypeSelect from './NoteTypeSelect'
import IdDisplay from './IdDisplay' import IdDisplay from './IdDisplay'
import ReloadIcon from '../../Icons/ReloadIcon' import ReloadIcon from '../../Icons/ReloadIcon'
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon' import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
import ObjectProperty from './ObjectProperty'
const { Text, Title } = Typography const { Text, Title } = Typography
const { TextArea } = Input const { TextArea } = Input
@ -621,7 +621,11 @@ const NotesPanel = ({ _id, onNewNote, type }) => {
{ required: true, message: 'Please select a note type' } { required: true, message: 'Please select a note type' }
]} ]}
> >
<NoteTypeSelect /> <ObjectProperty
type='object'
objectType='noteType'
isEditing={true}
/>
</Form.Item> </Form.Item>
</Flex> </Flex>
</Form> </Form>

View File

@ -47,7 +47,7 @@ const OperationDisplay = ({
} }
OperationDisplay.propTypes = { OperationDisplay.propTypes = {
operation: PropTypes.bool.isRequired, operation: PropTypes.string.isRequired,
showIcon: PropTypes.bool, showIcon: PropTypes.bool,
showText: PropTypes.bool, showText: PropTypes.bool,
showColor: PropTypes.bool showColor: PropTypes.bool

View File

@ -38,7 +38,7 @@ const ApiServerProvider = ({ children }) => {
const subscribedCallbacksRef = useRef(new Map()) const subscribedCallbacksRef = useRef(new Map())
const subscribedLockCallbacksRef = useRef(new Map()) const subscribedLockCallbacksRef = useRef(new Map())
const notifyLockUpdate = useCallback( const handleLockUpdate = useCallback(
async (lockData) => { async (lockData) => {
logger.debug('Notifying lock update:', lockData) logger.debug('Notifying lock update:', lockData)
const objectId = lockData._id || lockData.id const objectId = lockData._id || lockData.id
@ -94,7 +94,7 @@ const ApiServerProvider = ({ children }) => {
newSocket.on('objectUpdate', handleObjectUpdate) newSocket.on('objectUpdate', handleObjectUpdate)
newSocket.on('objectNew', handleObjectNew) newSocket.on('objectNew', handleObjectNew)
newSocket.on('notify_lock_update', notifyLockUpdate) newSocket.on('lockUpdate', handleLockUpdate)
newSocket.on('disconnect', () => { newSocket.on('disconnect', () => {
logger.debug('Api Server disconnected') logger.debug('Api Server disconnected')
@ -123,7 +123,7 @@ const ApiServerProvider = ({ children }) => {
socketRef.current = newSocket socketRef.current = newSocket
} }
}, [token, authenticated, messageApi, notificationApi, notifyLockUpdate]) }, [token, authenticated, messageApi, notificationApi, handleLockUpdate])
useEffect(() => { useEffect(() => {
if (token && authenticated == true) { if (token && authenticated == true) {

View File

@ -1,6 +1,5 @@
// src/contexts/PrintServerContext.js // src/contexts/PrintServerContext.js
import { createContext, useEffect, useState, useContext, useRef } from 'react' import { createContext, useEffect, useState, useContext, useRef } from 'react'
import io from 'socket.io-client'
import { message, notification } from 'antd' import { message, notification } from 'antd'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { AuthContext } from './AuthContext' import { AuthContext } from './AuthContext'
@ -21,47 +20,9 @@ const PrintServerProvider = ({ children }) => {
useEffect(() => { useEffect(() => {
if (token) { if (token) {
setConnecting(false)
setError(null)
log.debug('Token is available, connecting to print server...') log.debug('Token is available, connecting to print server...')
const newSocket = io(config.printServerUrl, {
reconnectionAttempts: 3,
timeout: 3000,
auth: { token: token }
})
setConnecting(true)
newSocket.on('connect', () => {
log.debug('Print server connected')
setConnecting(false)
setError(null)
})
newSocket.on('disconnect', () => {
log.debug('Print server disconnected')
setError('Print server disconnected')
})
newSocket.on('connect_error', (err) => {
log.error('Print server connection error:', err)
messageApi.error('Print server connection error: ' + err.message)
setError('Print server connection error')
})
newSocket.on('bridge.notification', (data) => {
notificationApi[data.type]({
title: data.title,
message: data.message
})
})
newSocket.on('error', (err) => {
log.error('Print server error:', err)
setError('Print server error')
})
socketRef.current = newSocket
// Clean up function // Clean up function
return () => { return () => {
if (socketRef.current) { if (socketRef.current) {

View File

@ -29,6 +29,7 @@ export const FilamentStock = {
], ],
filters: ['_id'], filters: ['_id'],
sorters: ['createdAt', 'updatedAt'], sorters: ['createdAt', 'updatedAt'],
group: ['filament'],
properties: [ properties: [
{ {
name: '_id', name: '_id',