Compare commits
13 Commits
2a18f3d697
...
cd83679232
| Author | SHA1 | Date | |
|---|---|---|---|
| cd83679232 | |||
| 38f03f8fe9 | |||
| 9a1f58aafe | |||
| a0ab5be6f2 | |||
| 5aa7355b0f | |||
| a505e1aaba | |||
| a5458c6b67 | |||
| 3bd2628960 | |||
| d46402983f | |||
| b6c2cb22f4 | |||
| 0a897e663c | |||
| 3ad0002bbb | |||
| b71537dc64 |
68
src/components/Dashboard/common/DeleteObjectModal.jsx
Normal file
68
src/components/Dashboard/common/DeleteObjectModal.jsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Modal, Button, Space, Typography } from 'antd'
|
||||||
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import BinIcon from '../../Icons/BinIcon'
|
||||||
|
|
||||||
|
const { Text } = Typography
|
||||||
|
|
||||||
|
const DeleteObjectModal = ({
|
||||||
|
open,
|
||||||
|
onOk,
|
||||||
|
onCancel,
|
||||||
|
loading,
|
||||||
|
objectType,
|
||||||
|
objectName
|
||||||
|
}) => {
|
||||||
|
const model = getModelByName(objectType)
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title={
|
||||||
|
<Space size={'middle'}>
|
||||||
|
<BinIcon />
|
||||||
|
{`Confirm Delete ${model.label}`}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
okText='Delete'
|
||||||
|
cancelText='Cancel'
|
||||||
|
okType='danger'
|
||||||
|
closable={false}
|
||||||
|
centered
|
||||||
|
maskClosable={false}
|
||||||
|
footer={[
|
||||||
|
<Button key='cancel' onClick={onCancel} disabled={loading}>
|
||||||
|
Cancel
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key='delete'
|
||||||
|
type='primary'
|
||||||
|
danger
|
||||||
|
onClick={onOk}
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
Are you sure you want to delete this {model.label.toLowerCase()}
|
||||||
|
{objectName ? ` "${objectName}"` : ''}?
|
||||||
|
</Text>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteObjectModal.propTypes = {
|
||||||
|
open: PropTypes.bool.isRequired,
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
loading: PropTypes.bool,
|
||||||
|
objectType: PropTypes.string.isRequired,
|
||||||
|
objectName: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeleteObjectModal
|
||||||
@ -49,7 +49,7 @@ const IdDisplay = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex align={'center'} className='iddisplay'>
|
<Flex align={'end'} className='iddisplay'>
|
||||||
{(() => {
|
{(() => {
|
||||||
const content = (
|
const content = (
|
||||||
<Flex gap={4}>
|
<Flex gap={4}>
|
||||||
|
|||||||
@ -12,33 +12,33 @@ const InfoCollapse = ({
|
|||||||
active,
|
active,
|
||||||
onToggle,
|
onToggle,
|
||||||
className = '',
|
className = '',
|
||||||
key = 'default'
|
collapseKey = 'default'
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Collapse
|
<Collapse
|
||||||
ghost
|
ghost
|
||||||
expandIconPosition='end'
|
expandIconPosition='end'
|
||||||
activeKey={active ? [key] : []}
|
activeKey={active ? [collapseKey] : []}
|
||||||
onChange={(keys) => onToggle(keys.length > 0)}
|
onChange={(keys) => onToggle(keys.length > 0)}
|
||||||
expandIcon={({ isActive }) => (
|
expandIcon={({ isActive }) => (
|
||||||
<CaretLeftOutlined rotate={isActive ? -90 : 0} />
|
<CaretLeftOutlined rotate={isActive ? -90 : 0} />
|
||||||
)}
|
)}
|
||||||
className={`no-h-padding-collapse ${className}`}
|
className={`no-h-padding-collapse ${className}`}
|
||||||
>
|
items={[
|
||||||
<Collapse.Panel
|
{
|
||||||
header={
|
key: collapseKey,
|
||||||
<Flex align='center' gap={'middle'}>
|
children: children,
|
||||||
{icon}
|
label: (
|
||||||
<Title level={5} style={{ margin: 0 }}>
|
<Flex align='center' gap={'middle'}>
|
||||||
{title}
|
{icon}
|
||||||
</Title>
|
<Title level={5} style={{ margin: 0 }}>
|
||||||
</Flex>
|
{title}
|
||||||
|
</Title>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
key={key}
|
]}
|
||||||
>
|
/>
|
||||||
{children}
|
|
||||||
</Collapse.Panel>
|
|
||||||
</Collapse>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ InfoCollapse.propTypes = {
|
|||||||
active: PropTypes.bool.isRequired,
|
active: PropTypes.bool.isRequired,
|
||||||
onToggle: PropTypes.func.isRequired,
|
onToggle: PropTypes.func.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
key: PropTypes.string
|
collapseKey: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InfoCollapse
|
export default InfoCollapse
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import MarkdownDisplay from './MarkdownDisplay'
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
import NoteTypeSelect from './NoteTypeSelect'
|
import NoteTypeSelect from './NoteTypeSelect'
|
||||||
import IdDisplay from './IdDisplay'
|
import IdDisplay from './IdDisplay'
|
||||||
@ -259,7 +260,7 @@ NoteItem.propTypes = {
|
|||||||
onChildNoteAdded: PropTypes.func
|
onChildNoteAdded: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
const NotesPanel = ({ _id, onNewNote }) => {
|
const NotesPanel = ({ _id, onNewNote, type }) => {
|
||||||
const [newNoteOpen, setNewNoteOpen] = useState(false)
|
const [newNoteOpen, setNewNoteOpen] = useState(false)
|
||||||
const [showMarkdown, setShowMarkdown] = useState(false)
|
const [showMarkdown, setShowMarkdown] = useState(false)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
@ -273,6 +274,7 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
const [expandedNotes, setExpandedNotes] = useState({})
|
const [expandedNotes, setExpandedNotes] = useState({})
|
||||||
const [newNoteForm] = Form.useForm()
|
const [newNoteForm] = Form.useForm()
|
||||||
const [selectedParentId, setSelectedParentId] = useState(null)
|
const [selectedParentId, setSelectedParentId] = useState(null)
|
||||||
|
const [selectedParentType, setSelectedParentType] = useState(null)
|
||||||
const [childNoteCallbacks, setChildNoteCallbacks] = useState({})
|
const [childNoteCallbacks, setChildNoteCallbacks] = useState({})
|
||||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
|
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
|
||||||
const [noteToDelete, setNoteToDelete] = useState(null)
|
const [noteToDelete, setNoteToDelete] = useState(null)
|
||||||
@ -289,30 +291,33 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
}, [newNoteForm, newNoteFormUpdateValues])
|
}, [newNoteForm, newNoteFormUpdateValues])
|
||||||
|
|
||||||
const { authenticated, userProfile } = useContext(AuthContext)
|
const { authenticated, userProfile } = useContext(AuthContext)
|
||||||
|
const { fetchNotes } = useContext(ApiServerContext)
|
||||||
|
|
||||||
const fetchData = useCallback(async (id) => {
|
const fetchData = useCallback(
|
||||||
try {
|
async (id) => {
|
||||||
const response = await axios.get(`${config.backendUrl}/notes`, {
|
try {
|
||||||
params: {
|
const newData = await fetchNotes(id)
|
||||||
parent: id,
|
setLoading(false)
|
||||||
sort: 'createdAt',
|
return newData
|
||||||
order: 'ascend'
|
} catch (error) {
|
||||||
},
|
setNotes([])
|
||||||
headers: {
|
setError(error)
|
||||||
Accept: 'application/json'
|
setLoading(false)
|
||||||
},
|
}
|
||||||
withCredentials: true
|
},
|
||||||
})
|
[fetchNotes]
|
||||||
|
)
|
||||||
|
|
||||||
const newData = response.data
|
const handleNewChildNote = useCallback(
|
||||||
setLoading(false)
|
(parentId) => {
|
||||||
return newData
|
setSelectedParentId(parentId)
|
||||||
} catch (error) {
|
setSelectedParentType('note')
|
||||||
setNotes([])
|
setNewNoteOpen(true)
|
||||||
setError(error)
|
newNoteForm.resetFields()
|
||||||
setLoading(false)
|
setNewNoteFormValues({})
|
||||||
}
|
},
|
||||||
}, [])
|
[newNoteForm]
|
||||||
|
)
|
||||||
|
|
||||||
const generateNotes = useCallback(
|
const generateNotes = useCallback(
|
||||||
async (id) => {
|
async (id) => {
|
||||||
@ -345,7 +350,7 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
expandedNotes={expandedNotes}
|
expandedNotes={expandedNotes}
|
||||||
setExpandedNotes={setExpandedNotes}
|
setExpandedNotes={setExpandedNotes}
|
||||||
fetchData={fetchData}
|
fetchData={fetchData}
|
||||||
onNewNote={handleNewNoteFromDropdown}
|
onNewNote={handleNewChildNote}
|
||||||
onDeleteNote={handleDeleteNote}
|
onDeleteNote={handleDeleteNote}
|
||||||
userProfile={userProfile}
|
userProfile={userProfile}
|
||||||
onChildNoteAdded={(noteId, callback) => {
|
onChildNoteAdded={(noteId, callback) => {
|
||||||
@ -357,7 +362,7 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
[loading, fetchData, expandedNotes, userProfile]
|
[loading, fetchData, expandedNotes, userProfile, handleNewChildNote]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleNewNote = async () => {
|
const handleNewNote = async () => {
|
||||||
@ -365,7 +370,11 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${config.backendUrl}/notes`,
|
`${config.backendUrl}/notes`,
|
||||||
{ ...newNoteFormValues, parent: selectedParentId || _id },
|
{
|
||||||
|
...newNoteFormValues,
|
||||||
|
parent: selectedParentId,
|
||||||
|
parentType: selectedParentType
|
||||||
|
},
|
||||||
{
|
{
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
}
|
}
|
||||||
@ -402,13 +411,6 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNewNoteFromDropdown = (parentId) => {
|
|
||||||
setSelectedParentId(parentId)
|
|
||||||
setNewNoteOpen(true)
|
|
||||||
newNoteForm.resetFields()
|
|
||||||
setNewNoteFormValues({})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDeleteNote = async (noteId) => {
|
const handleDeleteNote = async (noteId) => {
|
||||||
setNoteToDelete(noteId)
|
setNoteToDelete(noteId)
|
||||||
setDeleteConfirmOpen(true)
|
setDeleteConfirmOpen(true)
|
||||||
@ -499,7 +501,8 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
handleReloadData()
|
handleReloadData()
|
||||||
} else if (key === 'newNote') {
|
} else if (key === 'newNote') {
|
||||||
setSelectedParentId(null)
|
setSelectedParentId(_id)
|
||||||
|
setSelectedParentType(type)
|
||||||
setNewNoteOpen(true)
|
setNewNoteOpen(true)
|
||||||
newNoteForm.resetFields()
|
newNoteForm.resetFields()
|
||||||
setNewNoteFormValues({})
|
setNewNoteFormValues({})
|
||||||
@ -522,7 +525,8 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
icon={<PlusIcon />}
|
icon={<PlusIcon />}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedParentId(null)
|
setSelectedParentId(_id)
|
||||||
|
setSelectedParentType(type)
|
||||||
setNewNoteOpen(true)
|
setNewNoteOpen(true)
|
||||||
newNoteForm.resetFields()
|
newNoteForm.resetFields()
|
||||||
setNewNoteFormValues({})
|
setNewNoteFormValues({})
|
||||||
@ -688,6 +692,7 @@ const NotesPanel = ({ _id, onNewNote }) => {
|
|||||||
|
|
||||||
NotesPanel.propTypes = {
|
NotesPanel.propTypes = {
|
||||||
_id: PropTypes.string.isRequired,
|
_id: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
onNewNote: PropTypes.func
|
onNewNote: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ function mapActionsToMenuItems(actions, currentUrlWithActions, id) {
|
|||||||
const item = {
|
const item = {
|
||||||
key: action.key || action.name,
|
key: action.key || action.name,
|
||||||
label: action.label,
|
label: action.label,
|
||||||
|
danger: action?.danger || false,
|
||||||
icon: action.icon ? React.createElement(action.icon) : undefined,
|
icon: action.icon ? React.createElement(action.icon) : undefined,
|
||||||
disabled: actionUrl && actionUrl === currentUrlWithActions
|
disabled: actionUrl && actionUrl === currentUrlWithActions
|
||||||
}
|
}
|
||||||
@ -53,11 +54,10 @@ const ObjectActions = ({
|
|||||||
location.search
|
location.search
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('curr url', currentUrlWithoutActions)
|
|
||||||
|
|
||||||
// Filter out actions whose url matches currentUrl
|
|
||||||
const filteredActions = actions.filter(
|
const filteredActions = actions.filter(
|
||||||
(action) => !(action.url(id) && action.url(id) === currentUrlWithoutActions)
|
(action) =>
|
||||||
|
typeof action.url !== 'function' ||
|
||||||
|
action.url(id) !== currentUrlWithoutActions
|
||||||
)
|
)
|
||||||
|
|
||||||
const currentUrlWithActions = location.pathname + location.search
|
const currentUrlWithActions = location.pathname + location.search
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const ObjectInfo = ({
|
|||||||
properties = [],
|
properties = [],
|
||||||
required = undefined,
|
required = undefined,
|
||||||
visibleProperties = {},
|
visibleProperties = {},
|
||||||
|
objectPropertyProps = {},
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const allItems = getModelProperties(type)
|
const allItems = getModelProperties(type)
|
||||||
@ -55,6 +56,7 @@ const ObjectInfo = ({
|
|||||||
children: (
|
children: (
|
||||||
<ObjectProperty
|
<ObjectProperty
|
||||||
{...item}
|
{...item}
|
||||||
|
{...objectPropertyProps}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
/>
|
/>
|
||||||
@ -91,7 +93,8 @@ ObjectInfo.propTypes = {
|
|||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
objectData: PropTypes.object,
|
objectData: PropTypes.object,
|
||||||
required: PropTypes.bool,
|
required: PropTypes.bool,
|
||||||
visibleProperties: PropTypes.object
|
visibleProperties: PropTypes.object,
|
||||||
|
objectPropertyProps: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectInfo
|
export default ObjectInfo
|
||||||
|
|||||||
49
src/components/Dashboard/common/ObjectList.jsx
Normal file
49
src/components/Dashboard/common/ObjectList.jsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { List, Typography, Flex } from 'antd'
|
||||||
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import IdDisplay from './IdDisplay'
|
||||||
|
|
||||||
|
const { Text } = Typography
|
||||||
|
|
||||||
|
const ObjectList = ({ value, objectType, bordered = true }) => {
|
||||||
|
if (!value || !Array.isArray(value) || value.length === 0) {
|
||||||
|
return <Text type='secondary'>n/a</Text>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
size='small'
|
||||||
|
bordered={bordered}
|
||||||
|
dataSource={value}
|
||||||
|
renderItem={(item) => {
|
||||||
|
const model = getModelByName(objectType)
|
||||||
|
const Icon = model.icon
|
||||||
|
return (
|
||||||
|
<List.Item>
|
||||||
|
<Flex gap={'small'} align='center'>
|
||||||
|
<Icon />
|
||||||
|
{item?.name ? <Text ellipsis>{item.name}</Text> : null}
|
||||||
|
{item?._id ? (
|
||||||
|
<IdDisplay
|
||||||
|
id={item?._id}
|
||||||
|
longId={false}
|
||||||
|
type={objectType}
|
||||||
|
showHyperlink={true}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Flex>
|
||||||
|
</List.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectList.propTypes = {
|
||||||
|
value: PropTypes.array,
|
||||||
|
bordered: PropTypes.bool,
|
||||||
|
objectType: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ObjectList
|
||||||
@ -37,6 +37,10 @@ import { getPropertyValue } from '../../../database/ObjectModels'
|
|||||||
import PropertyChanges from './PropertyChanges'
|
import PropertyChanges from './PropertyChanges'
|
||||||
import NetGrossDisplay from './NetGrossDisplay'
|
import NetGrossDisplay from './NetGrossDisplay'
|
||||||
import NetGrossInput from './NetGrossInput'
|
import NetGrossInput from './NetGrossInput'
|
||||||
|
import ObjectList from './ObjectList'
|
||||||
|
import VarianceDisplay from './VarianceDisplay'
|
||||||
|
import OperationDisplay from './OperationDisplay'
|
||||||
|
import MarkdownDisplay from './MarkdownDisplay'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@ -71,7 +75,7 @@ const ObjectProperty = ({
|
|||||||
initial = false,
|
initial = false,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
if (typeof value == 'function' && objectData) {
|
if (value && typeof value == 'function' && objectData) {
|
||||||
value = value(objectData)
|
value = value(objectData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +91,14 @@ const ObjectProperty = ({
|
|||||||
difference = difference(objectData)
|
difference = difference(objectData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prefix && typeof prefix == 'function' && objectData) {
|
||||||
|
prefix = prefix(objectData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suffix && typeof suffix == 'function' && objectData) {
|
||||||
|
suffix = suffix(objectData)
|
||||||
|
}
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = getPropertyValue(objectData, name)
|
value = getPropertyValue(objectData, name)
|
||||||
}
|
}
|
||||||
@ -218,6 +230,19 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 'variance': {
|
||||||
|
if (value != null) {
|
||||||
|
return (
|
||||||
|
<VarianceDisplay value={value} prefix={prefix} suffix={suffix} />
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text type='secondary' {...textParams}>
|
||||||
|
n/a
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
case 'text':
|
case 'text':
|
||||||
if (value != null && value != '') {
|
if (value != null && value != '') {
|
||||||
return (
|
return (
|
||||||
@ -234,6 +259,16 @@ const ObjectProperty = ({
|
|||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case 'markdown':
|
||||||
|
if (value != null && value != '') {
|
||||||
|
return <MarkdownDisplay content={value} />
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text type='secondary' {...textParams}>
|
||||||
|
n/a
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
case 'email':
|
case 'email':
|
||||||
if (value != null && value != '') {
|
if (value != null && value != '') {
|
||||||
return <EmailDisplay email={value} />
|
return <EmailDisplay email={value} />
|
||||||
@ -265,6 +300,9 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 'objectList': {
|
||||||
|
return <ObjectList value={value} objectType={objectType} />
|
||||||
|
}
|
||||||
case 'state': {
|
case 'state': {
|
||||||
if (value && value?.type) {
|
if (value && value?.type) {
|
||||||
switch (objectType) {
|
switch (objectType) {
|
||||||
@ -346,6 +384,17 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 'operation': {
|
||||||
|
if (value != null) {
|
||||||
|
return <OperationDisplay operation={value} />
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text type='secondary' {...textParams}>
|
||||||
|
n/a
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
case 'propertyChanges': {
|
case 'propertyChanges': {
|
||||||
return <PropertyChanges type={objectType} value={value} />
|
return <PropertyChanges type={objectType} value={value} />
|
||||||
}
|
}
|
||||||
@ -555,7 +604,7 @@ const ObjectProperty = ({
|
|||||||
<PrinterSelect placeholder={label} />
|
<PrinterSelect placeholder={label} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
case 'gcodefile':
|
case 'gcodeFile':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<GCodeFileSelect placeholder={label} />
|
<GCodeFileSelect placeholder={label} />
|
||||||
@ -603,20 +652,22 @@ const ObjectProperty = ({
|
|||||||
|
|
||||||
ObjectProperty.propTypes = {
|
ObjectProperty.propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
value: PropTypes.any,
|
value: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
formItemProps: PropTypes.object,
|
formItemProps: PropTypes.object,
|
||||||
required: PropTypes.bool,
|
required: PropTypes.bool,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
prefix: PropTypes.string,
|
prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
suffix: PropTypes.string,
|
suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
min: PropTypes.number,
|
min: PropTypes.number,
|
||||||
max: PropTypes.number,
|
max: PropTypes.number,
|
||||||
step: PropTypes.number,
|
step: PropTypes.number,
|
||||||
showLabel: PropTypes.bool,
|
showLabel: PropTypes.bool,
|
||||||
objectType: PropTypes.string,
|
objectType: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
readOnly: PropTypes.bool,
|
readOnly: PropTypes.bool,
|
||||||
disabled: PropTypes.bool
|
disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
||||||
|
difference: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
|
||||||
|
objectData: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectProperty
|
export default ObjectProperty
|
||||||
|
|||||||
57
src/components/Dashboard/common/OperationDisplay.jsx
Normal file
57
src/components/Dashboard/common/OperationDisplay.jsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Space, Tag } from 'antd'
|
||||||
|
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
|
||||||
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
|
import BinIcon from '../../Icons/BinIcon'
|
||||||
|
import EditIcon from '../../Icons/EditIcon'
|
||||||
|
|
||||||
|
const OperationDisplay = ({
|
||||||
|
operation,
|
||||||
|
showIcon = true,
|
||||||
|
showText = true,
|
||||||
|
showColor = true
|
||||||
|
}) => {
|
||||||
|
var tagText = 'False'
|
||||||
|
var tagIcon = <QuestionCircleIcon />
|
||||||
|
var tagColor = 'default'
|
||||||
|
switch (operation) {
|
||||||
|
case 'new':
|
||||||
|
tagText = 'New'
|
||||||
|
tagIcon = <PlusIcon />
|
||||||
|
tagColor = 'success'
|
||||||
|
break
|
||||||
|
case 'delete':
|
||||||
|
tagText = 'Delete'
|
||||||
|
tagIcon = <BinIcon />
|
||||||
|
tagColor = 'error'
|
||||||
|
break
|
||||||
|
case 'edit':
|
||||||
|
tagText = 'Edit'
|
||||||
|
tagIcon = <EditIcon />
|
||||||
|
tagColor = 'blue'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Tag
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
color={showColor ? tagColor : 'default'}
|
||||||
|
icon={showIcon ? tagIcon : undefined}
|
||||||
|
>
|
||||||
|
{showText ? tagText : null}
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OperationDisplay.propTypes = {
|
||||||
|
operation: PropTypes.bool.isRequired,
|
||||||
|
showIcon: PropTypes.bool,
|
||||||
|
showText: PropTypes.bool,
|
||||||
|
showColor: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OperationDisplay
|
||||||
@ -8,7 +8,7 @@ import ArrowRightIcon from '../../Icons/ArrowRightIcon'
|
|||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const PropertyChanges = ({ type, value }) => {
|
const PropertyChanges = ({ type, value }) => {
|
||||||
if (!value || !value.new) {
|
if (!value || (!value.new && !value.old)) {
|
||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>n/a</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,7 +18,15 @@ const PropertyChanges = ({ type, value }) => {
|
|||||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
const val = obj[key]
|
const val = obj[key]
|
||||||
const newKey = prefix ? `${prefix}.${key}` : key
|
const newKey = prefix ? `${prefix}.${key}` : key
|
||||||
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
||||||
|
// Don't flatten keys that are "state" or "netGross"
|
||||||
|
if (
|
||||||
|
val &&
|
||||||
|
typeof val === 'object' &&
|
||||||
|
!Array.isArray(val) &&
|
||||||
|
key !== 'state' &&
|
||||||
|
key !== 'netGross'
|
||||||
|
) {
|
||||||
flattenObject(val, newKey, res)
|
flattenObject(val, newKey, res)
|
||||||
} else {
|
} else {
|
||||||
res[newKey] = val
|
res[newKey] = val
|
||||||
@ -34,7 +42,6 @@ const PropertyChanges = ({ type, value }) => {
|
|||||||
...flatOld,
|
...flatOld,
|
||||||
...flatNew
|
...flatNew
|
||||||
}
|
}
|
||||||
console.log('combined', combinedChanges)
|
|
||||||
return (
|
return (
|
||||||
<Descriptions size='small' column={1}>
|
<Descriptions size='small' column={1}>
|
||||||
{Object.keys(combinedChanges).map((key) => {
|
{Object.keys(combinedChanges).map((key) => {
|
||||||
@ -46,19 +53,25 @@ const PropertyChanges = ({ type, value }) => {
|
|||||||
return (
|
return (
|
||||||
<Descriptions.Item key={key} label={changeProperty.label}>
|
<Descriptions.Item key={key} label={changeProperty.label}>
|
||||||
<Space>
|
<Space>
|
||||||
<ObjectProperty
|
{value?.old ? (
|
||||||
{...changeProperty}
|
<ObjectProperty
|
||||||
longId={false}
|
{...changeProperty}
|
||||||
objectData={value?.old}
|
longId={false}
|
||||||
/>
|
objectData={value?.old}
|
||||||
<Text type='secondary'>
|
/>
|
||||||
<ArrowRightIcon />
|
) : null}
|
||||||
</Text>
|
{value?.old && value?.new ? (
|
||||||
<ObjectProperty
|
<Text type='secondary'>
|
||||||
{...changeProperty}
|
<ArrowRightIcon />
|
||||||
longId={false}
|
</Text>
|
||||||
objectData={value?.new}
|
) : null}
|
||||||
/>
|
{value?.new ? (
|
||||||
|
<ObjectProperty
|
||||||
|
{...changeProperty}
|
||||||
|
longId={false}
|
||||||
|
objectData={value?.new}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
)
|
)
|
||||||
|
|||||||
32
src/components/Dashboard/common/VarianceDisplay.jsx
Normal file
32
src/components/Dashboard/common/VarianceDisplay.jsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Typography } from 'antd'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
const { Text } = Typography
|
||||||
|
|
||||||
|
const VarianceDisplay = ({ value, prefix, suffix }) => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return <Text type='secondary'>n/a</Text>
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPositive = value > 0
|
||||||
|
const isNegative = value < 0
|
||||||
|
const displayValue = Math.abs(value).toFixed(2)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text type={isPositive ? 'success' : isNegative ? 'danger' : undefined}>
|
||||||
|
{prefix || ''}
|
||||||
|
{isPositive ? '+' : isNegative ? '-' : ''}
|
||||||
|
{displayValue}
|
||||||
|
{suffix || ''}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
VarianceDisplay.propTypes = {
|
||||||
|
value: PropTypes.number,
|
||||||
|
prefix: PropTypes.string,
|
||||||
|
suffix: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VarianceDisplay
|
||||||
@ -4,7 +4,8 @@ import React, {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
useContext,
|
useContext,
|
||||||
useRef
|
useRef,
|
||||||
|
useCallback
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import io from 'socket.io-client'
|
import io from 'socket.io-client'
|
||||||
import { message, notification, Modal, Space, Button } from 'antd'
|
import { message, notification, Modal, Space, Button } from 'antd'
|
||||||
@ -22,8 +23,9 @@ logger.setLevel(config.logLevel)
|
|||||||
const ApiServerContext = createContext()
|
const ApiServerContext = createContext()
|
||||||
|
|
||||||
const ApiServerProvider = ({ children }) => {
|
const ApiServerProvider = ({ children }) => {
|
||||||
const { token, userProfile } = useContext(AuthContext)
|
const { token, userProfile, authenticated } = useContext(AuthContext)
|
||||||
const socketRef = useRef(null)
|
const socketRef = useRef(null)
|
||||||
|
const [connected, setConnected] = useState(false)
|
||||||
const [connecting, setConnecting] = useState(false)
|
const [connecting, setConnecting] = useState(false)
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
@ -32,9 +34,43 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
const [showErrorModal, setShowErrorModal] = useState(false)
|
const [showErrorModal, setShowErrorModal] = useState(false)
|
||||||
const [errorModalContent, setErrorModalContent] = useState('')
|
const [errorModalContent, setErrorModalContent] = useState('')
|
||||||
const [retryCallback, setRetryCallback] = useState(null)
|
const [retryCallback, setRetryCallback] = useState(null)
|
||||||
|
const subscribedCallbacksRef = useRef(new Map())
|
||||||
|
const subscribedLockCallbacksRef = useRef(new Map())
|
||||||
|
|
||||||
useEffect(() => {
|
const notifyLockUpdate = useCallback(
|
||||||
if (token) {
|
async (lockData) => {
|
||||||
|
logger.debug('Notifying lock update:', lockData)
|
||||||
|
const objectId = lockData._id || lockData.id
|
||||||
|
|
||||||
|
if (
|
||||||
|
objectId &&
|
||||||
|
subscribedLockCallbacksRef.current.has(objectId) &&
|
||||||
|
lockData.user != userProfile?._id
|
||||||
|
) {
|
||||||
|
const callbacks = subscribedLockCallbacksRef.current.get(objectId)
|
||||||
|
logger.debug(
|
||||||
|
`Calling ${callbacks.length} lock callbacks for object:`,
|
||||||
|
objectId
|
||||||
|
)
|
||||||
|
callbacks.forEach((callback) => {
|
||||||
|
try {
|
||||||
|
callback(lockData)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error in lock update callback:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.debug(
|
||||||
|
`No lock callbacks found for object: ${objectId}, subscribed lock callbacks:`,
|
||||||
|
Array.from(subscribedLockCallbacksRef.current.keys())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[userProfile?._id]
|
||||||
|
)
|
||||||
|
|
||||||
|
const connectToServer = useCallback(() => {
|
||||||
|
if (token && authenticated == true) {
|
||||||
logger.debug('Token is available, connecting to api server...')
|
logger.debug('Token is available, connecting to api server...')
|
||||||
|
|
||||||
const newSocket = io(config.apiServerUrl, {
|
const newSocket = io(config.apiServerUrl, {
|
||||||
@ -48,18 +84,25 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
newSocket.on('connect', () => {
|
newSocket.on('connect', () => {
|
||||||
logger.debug('Api Server connected')
|
logger.debug('Api Server connected')
|
||||||
setConnecting(false)
|
setConnecting(false)
|
||||||
|
setConnected(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
newSocket.on('notify_object_update', notifyObjectUpdate)
|
||||||
|
newSocket.on('notify_object_new', notifyObjectNew)
|
||||||
|
newSocket.on('notify_lock_update', notifyLockUpdate)
|
||||||
|
|
||||||
newSocket.on('disconnect', () => {
|
newSocket.on('disconnect', () => {
|
||||||
logger.debug('Api Server disconnected')
|
logger.debug('Api Server disconnected')
|
||||||
setError('Api Server disconnected')
|
setError('Api Server disconnected')
|
||||||
|
setConnected(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
newSocket.on('connect_error', (err) => {
|
newSocket.on('connect_error', (err) => {
|
||||||
logger.error('Api Server connection error:', err)
|
logger.error('Api Server connection error:', err)
|
||||||
messageApi.error('Api Server connection error: ' + err.message)
|
messageApi.error('Api Server connection error: ' + err.message)
|
||||||
setError('Api Server connection error')
|
setError('Api Server connection error')
|
||||||
|
setConnected(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
newSocket.on('bridge.notification', (data) => {
|
newSocket.on('bridge.notification', (data) => {
|
||||||
@ -75,21 +118,27 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
socketRef.current = newSocket
|
socketRef.current = newSocket
|
||||||
|
}
|
||||||
|
}, [token, authenticated, messageApi, notificationApi, notifyLockUpdate])
|
||||||
|
|
||||||
// Clean up function
|
useEffect(() => {
|
||||||
return () => {
|
if (token && authenticated == true) {
|
||||||
if (socketRef.current) {
|
connectToServer()
|
||||||
logger.debug('Cleaning up api server connection...')
|
|
||||||
socketRef.current.disconnect()
|
|
||||||
socketRef.current = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (!token && socketRef.current) {
|
} else if (!token && socketRef.current) {
|
||||||
logger.debug('Token not available, disconnecting api server...')
|
logger.debug('Token not available, disconnecting api server...')
|
||||||
socketRef.current.disconnect()
|
socketRef.current.disconnect()
|
||||||
socketRef.current = null
|
socketRef.current = null
|
||||||
}
|
}
|
||||||
}, [token, messageApi])
|
|
||||||
|
// Clean up function
|
||||||
|
return () => {
|
||||||
|
if (socketRef.current) {
|
||||||
|
logger.debug('Cleaning up api server connection...')
|
||||||
|
socketRef.current.disconnect()
|
||||||
|
socketRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [token, authenticated, connectToServer])
|
||||||
|
|
||||||
const lockObject = (id, type) => {
|
const lockObject = (id, type) => {
|
||||||
logger.debug('Locking ' + id)
|
logger.debug('Locking ' + id)
|
||||||
@ -118,8 +167,12 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
type: type
|
type: type
|
||||||
},
|
},
|
||||||
(lockEvent) => {
|
(lockEvent) => {
|
||||||
logger.debug('Received lock event for object:', id, lockEvent)
|
logger.debug('Received lock status for object:', id, lockEvent)
|
||||||
resolve(lockEvent)
|
if (lockEvent.user != userProfile?._id) {
|
||||||
|
resolve(lockEvent)
|
||||||
|
} else {
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
logger.debug('Sent fetch lock command for object:', id)
|
logger.debug('Sent fetch lock command for object:', id)
|
||||||
@ -127,65 +180,186 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onLockEvent = (id, callback) => {
|
const notifyObjectUpdate = async (object) => {
|
||||||
|
logger.debug('Notifying object update:', object)
|
||||||
|
const objectId = object._id || object.id
|
||||||
|
|
||||||
|
if (objectId && subscribedCallbacksRef.current.has(objectId)) {
|
||||||
|
const callbacks = subscribedCallbacksRef.current.get(objectId)
|
||||||
|
logger.debug(
|
||||||
|
`Calling ${callbacks.length} callbacks for object:`,
|
||||||
|
objectId
|
||||||
|
)
|
||||||
|
callbacks.forEach((callback) => {
|
||||||
|
try {
|
||||||
|
callback(object)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error in object update callback:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.debug(
|
||||||
|
`No callbacks found for object: ${objectId}, subscribed callbacks:`,
|
||||||
|
Array.from(subscribedCallbacksRef.current.keys())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifyObjectNew = async (object) => {
|
||||||
|
logger.debug('Notifying object new:', object)
|
||||||
|
const objectType = object.type || 'unknown'
|
||||||
|
|
||||||
|
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
|
||||||
|
const callbacks = subscribedCallbacksRef.current.get(objectType)
|
||||||
|
logger.debug(
|
||||||
|
`Calling ${callbacks.length} callbacks for type:`,
|
||||||
|
objectType
|
||||||
|
)
|
||||||
|
callbacks.forEach((callback) => {
|
||||||
|
try {
|
||||||
|
callback(object)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error in object new callback:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.debug(
|
||||||
|
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
|
||||||
|
Array.from(subscribedCallbacksRef.current.keys())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const offUpdateEvent = useCallback((id, type, callback) => {
|
||||||
if (socketRef.current && socketRef.current.connected == true) {
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
const eventHandler = (data) => {
|
// Remove callback from the subscribed callbacks map
|
||||||
if (data._id === id && data?.user !== userProfile._id) {
|
if (subscribedCallbacksRef.current.has(id)) {
|
||||||
logger.debug(
|
const callbacks = subscribedCallbacksRef.current
|
||||||
'Lock update received for object:',
|
.get(id)
|
||||||
id,
|
.filter((cb) => cb !== callback)
|
||||||
'locked:',
|
if (callbacks.length === 0) {
|
||||||
data.locked
|
subscribedCallbacksRef.current.delete(id)
|
||||||
)
|
socketRef.current.emit('unsubscribe', { id: id, type: type })
|
||||||
callback(data)
|
} else {
|
||||||
|
subscribedCallbacksRef.current.set(id, callbacks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
socketRef.current.on('notify_lock_update', eventHandler)
|
|
||||||
logger.debug('Registered lock event listener for object:', id)
|
|
||||||
|
|
||||||
// Return cleanup function
|
|
||||||
return () => offLockEvent(id, eventHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const offLockEvent = (id, eventHandler) => {
|
|
||||||
if (socketRef.current && socketRef.current.connected == true) {
|
|
||||||
socketRef.current.off('notify_lock_update', eventHandler)
|
|
||||||
logger.debug('Removed lock event listener for object:', id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onUpdateEvent = (id, callback) => {
|
|
||||||
if (socketRef.current && socketRef.current.connected == true) {
|
|
||||||
const eventHandler = (data) => {
|
|
||||||
if (data._id === id && data?.user !== userProfile._id) {
|
|
||||||
logger.debug(
|
|
||||||
'Update event received for object:',
|
|
||||||
id,
|
|
||||||
'updatedAt:',
|
|
||||||
data.updatedAt
|
|
||||||
)
|
|
||||||
callback(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socketRef.current.on('notify_object_update', eventHandler)
|
|
||||||
logger.debug('Registered update event listener for object:', id)
|
|
||||||
|
|
||||||
// Return cleanup function
|
|
||||||
return () => offUpdateEvent(id, eventHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const offUpdateEvent = (id, eventHandler) => {
|
|
||||||
if (socketRef.current && socketRef.current.connected == true) {
|
|
||||||
socketRef.current.off('notify_update', eventHandler)
|
|
||||||
logger.debug('Removed update event listener for object:', id)
|
logger.debug('Removed update event listener for object:', id)
|
||||||
}
|
}
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const showError = (content, callback = null) => {
|
const offTypeEvent = useCallback((type, callback) => {
|
||||||
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
|
// Remove callback from the subscribed callbacks map
|
||||||
|
if (subscribedCallbacksRef.current.has(type)) {
|
||||||
|
const callbacks = subscribedCallbacksRef.current
|
||||||
|
.get(type)
|
||||||
|
.filter((cb) => cb !== callback)
|
||||||
|
if (callbacks.length === 0) {
|
||||||
|
subscribedCallbacksRef.current.delete(type)
|
||||||
|
socketRef.current.emit('unsubscribe', { type: type })
|
||||||
|
} else {
|
||||||
|
subscribedCallbacksRef.current.set(type, callbacks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('Removed new event listener for type:', type)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const subscribeToObject = useCallback(
|
||||||
|
(id, type, callback) => {
|
||||||
|
logger.debug('Subscribing to object:', id, 'type:', type)
|
||||||
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
|
// Add callback to the subscribed callbacks map immediately
|
||||||
|
if (!subscribedCallbacksRef.current.has(id)) {
|
||||||
|
subscribedCallbacksRef.current.set(id, [])
|
||||||
|
}
|
||||||
|
subscribedCallbacksRef.current.get(id).push(callback)
|
||||||
|
logger.debug(
|
||||||
|
`Added callback for object ${id}, total callbacks: ${subscribedCallbacksRef.current.get(id).length}`
|
||||||
|
)
|
||||||
|
|
||||||
|
socketRef.current.emit('subscribe', { id: id, type: type })
|
||||||
|
logger.debug('Registered update event listener for object:', id)
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
return () => offUpdateEvent(id, type, callback)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[offUpdateEvent]
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscribeToType = useCallback(
|
||||||
|
(type, callback) => {
|
||||||
|
logger.debug('Subscribing to type:', type)
|
||||||
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
|
// Add callback to the subscribed callbacks map immediately
|
||||||
|
if (!subscribedCallbacksRef.current.has(type)) {
|
||||||
|
subscribedCallbacksRef.current.set(type, [])
|
||||||
|
}
|
||||||
|
subscribedCallbacksRef.current.get(type).push(callback)
|
||||||
|
logger.debug(
|
||||||
|
`Added callback for type ${type}, total callbacks: ${subscribedCallbacksRef.current.get(type).length}`
|
||||||
|
)
|
||||||
|
|
||||||
|
socketRef.current.emit('subscribe', { type: type })
|
||||||
|
logger.debug('Registered update event listener for object:', type)
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
return () => offTypeEvent(type, callback)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[offTypeEvent]
|
||||||
|
)
|
||||||
|
|
||||||
|
const offLockEvent = useCallback((id, callback) => {
|
||||||
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
|
// Remove callback from the subscribed lock callbacks map
|
||||||
|
if (subscribedLockCallbacksRef.current.has(id)) {
|
||||||
|
const callbacks = subscribedLockCallbacksRef.current
|
||||||
|
.get(id)
|
||||||
|
.filter((cb) => cb !== callback)
|
||||||
|
if (callbacks.length === 0) {
|
||||||
|
subscribedLockCallbacksRef.current.delete(id)
|
||||||
|
} else {
|
||||||
|
subscribedLockCallbacksRef.current.set(id, callbacks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('Removed lock event listener for object:', id)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const subscribeToLock = useCallback(
|
||||||
|
(id, type, callback) => {
|
||||||
|
logger.debug('Subscribing to lock for object:', id, 'type:', type)
|
||||||
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
|
// Add callback to the subscribed lock callbacks map immediately
|
||||||
|
if (!subscribedLockCallbacksRef.current.has(id)) {
|
||||||
|
subscribedLockCallbacksRef.current.set(id, [])
|
||||||
|
}
|
||||||
|
subscribedLockCallbacksRef.current.get(id).push(callback)
|
||||||
|
logger.debug(
|
||||||
|
`Added lock callback for object ${id}, total lock callbacks: ${subscribedLockCallbacksRef.current.get(id).length}`
|
||||||
|
)
|
||||||
|
|
||||||
|
socketRef.current.emit('subscribe_lock', { id: id, type: type })
|
||||||
|
logger.debug('Registered lock event listener for object:', id)
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
return () => offLockEvent(id, callback)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[offLockEvent]
|
||||||
|
)
|
||||||
|
|
||||||
|
const showError = (error, callback = null) => {
|
||||||
|
var content = `Error ${error.code} (${error.status}): ${error.message}`
|
||||||
|
if (error.response?.data?.error) {
|
||||||
|
content = `${error.response?.data?.error} (${error.status})`
|
||||||
|
}
|
||||||
setErrorModalContent(content)
|
setErrorModalContent(content)
|
||||||
setRetryCallback(() => callback)
|
setRetryCallback(() => callback)
|
||||||
setShowErrorModal(true)
|
setShowErrorModal(true)
|
||||||
@ -200,8 +374,8 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
setRetryCallback(null)
|
setRetryCallback(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generalized fetchObjectInfo function
|
// Generalized fetchObject function
|
||||||
const fetchObjectInfo = async (id, type) => {
|
const fetchObject = async (id, type) => {
|
||||||
const fetchUrl = `${config.backendUrl}/${type}s/${id}`
|
const fetchUrl = `${config.backendUrl}/${type}s/${id}`
|
||||||
setFetchLoading(true)
|
setFetchLoading(true)
|
||||||
logger.debug('Fetching from ' + fetchUrl)
|
logger.debug('Fetching from ' + fetchUrl)
|
||||||
@ -214,62 +388,17 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Failed to fetch object information:', err)
|
showError(err, () => {
|
||||||
// Don't automatically show error - let the component handle it
|
fetchObject(id, type)
|
||||||
throw err
|
})
|
||||||
|
return {}
|
||||||
} finally {
|
} finally {
|
||||||
setFetchLoading(false)
|
setFetchLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update filament information
|
|
||||||
const updateObjectInfo = async (id, type, value) => {
|
|
||||||
const updateUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
|
|
||||||
logger.debug('Updating info for ' + id)
|
|
||||||
try {
|
|
||||||
const response = await axios.put(updateUrl, value, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
withCredentials: true
|
|
||||||
})
|
|
||||||
logger.debug('Filament updated successfully')
|
|
||||||
if (socketRef.current && socketRef.current.connected == true) {
|
|
||||||
await socketRef.current.emit('update', {
|
|
||||||
_id: id,
|
|
||||||
type: type,
|
|
||||||
updatedAt: response.data.updatedAt
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return response.data
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Failed to update filament information:', err)
|
|
||||||
// Don't automatically show error - let the component handle it
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update filament information
|
|
||||||
const createObject = async (type, value) => {
|
|
||||||
const createUrl = `${config.backendUrl}/${type.toLowerCase()}s`
|
|
||||||
logger.debug('Creating object...')
|
|
||||||
try {
|
|
||||||
const response = await axios.post(createUrl, value, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
withCredentials: true
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Failed to update filament information:', err)
|
|
||||||
// Don't automatically show error - let the component handle it
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch table data with pagination, filtering, and sorting
|
// Fetch table data with pagination, filtering, and sorting
|
||||||
const fetchTableData = async (type, params = {}) => {
|
const fetchObjects = async (type, params = {}) => {
|
||||||
const {
|
const {
|
||||||
page = 1,
|
page = 1,
|
||||||
limit = 25,
|
limit = 25,
|
||||||
@ -319,9 +448,122 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
hasMore,
|
hasMore,
|
||||||
page
|
page
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
logger.error('Failed to fetch table data:', error)
|
showError(err, () => {
|
||||||
throw error
|
fetchObjects(type, params)
|
||||||
|
})
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch table data with pagination, filtering, and sorting
|
||||||
|
const fetchObjectsByProperty = async (type, params = {}) => {
|
||||||
|
const { filter = {}, properties = [] } = params
|
||||||
|
|
||||||
|
logger.debug('Fetching property object data from:', type, {
|
||||||
|
properties,
|
||||||
|
filter
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${config.backendUrl}/${type.toLowerCase()}s/properties`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
...filter,
|
||||||
|
properties: properties.join(',') // Convert array to comma-separated string
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json'
|
||||||
|
},
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const newData = response.data
|
||||||
|
|
||||||
|
return newData
|
||||||
|
} catch (err) {
|
||||||
|
showError(err, () => {
|
||||||
|
fetchObjectsByProperty(type, params)
|
||||||
|
})
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update filament information
|
||||||
|
const updateObject = async (id, type, value) => {
|
||||||
|
const updateUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
|
||||||
|
logger.debug('Updating info for ' + id)
|
||||||
|
try {
|
||||||
|
const response = await axios.put(updateUrl, value, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
logger.debug('Object updated successfully')
|
||||||
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
|
await socketRef.current.emit('update', {
|
||||||
|
_id: id,
|
||||||
|
type: type,
|
||||||
|
updatedAt: response.data.updatedAt
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response.data
|
||||||
|
} catch (err) {
|
||||||
|
setError(err, () => {
|
||||||
|
updateObject(id, type, value)
|
||||||
|
})
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update filament information
|
||||||
|
const deleteObject = async (id, type) => {
|
||||||
|
const deleteUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
|
||||||
|
logger.debug('Deleting object ID: ' + id)
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(deleteUrl, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
logger.debug('Object deleted successfully')
|
||||||
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
|
await socketRef.current.emit('update', {
|
||||||
|
_id: id,
|
||||||
|
type: type,
|
||||||
|
updatedAt: response.data.updatedAt
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response.data
|
||||||
|
} catch (err) {
|
||||||
|
showError(err, () => {
|
||||||
|
deleteObject(id, type)
|
||||||
|
})
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update filament information
|
||||||
|
const createObject = async (type, value) => {
|
||||||
|
const createUrl = `${config.backendUrl}/${type.toLowerCase()}s`
|
||||||
|
logger.debug('Creating object...')
|
||||||
|
try {
|
||||||
|
const response = await axios.post(createUrl, value, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
} catch (err) {
|
||||||
|
showError(err, () => {
|
||||||
|
createObject(type, value)
|
||||||
|
})
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,27 +590,35 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
document.body.appendChild(fileLink)
|
document.body.appendChild(fileLink)
|
||||||
fileLink.click()
|
fileLink.click()
|
||||||
fileLink.parentNode.removeChild(fileLink)
|
fileLink.parentNode.removeChild(fileLink)
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
logger.error('Failed to download GCode file content:', error)
|
showError(err, () => {
|
||||||
|
fetchObjectContent(id, type, fileName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (error.response) {
|
// Fetch notes for a specific parent
|
||||||
if (error.response.status === 404) {
|
const fetchNotes = async (parentId) => {
|
||||||
showError(
|
logger.debug('Fetching notes for parent:', parentId)
|
||||||
`The ${type} file "${fileName}" was not found on the server. It may have been deleted or moved.`,
|
try {
|
||||||
() => fetchObjectContent(id, type, fileName)
|
const response = await axios.get(`${config.backendUrl}/notes`, {
|
||||||
)
|
params: {
|
||||||
} else {
|
parent: parentId,
|
||||||
showError(
|
sort: 'createdAt',
|
||||||
`Error downloading ${type} file: ${error.response.status} - ${error.response.statusText}`,
|
order: 'ascend'
|
||||||
() => fetchObjectContent(id, type, fileName)
|
},
|
||||||
)
|
headers: {
|
||||||
}
|
Accept: 'application/json'
|
||||||
} else {
|
},
|
||||||
showError(
|
withCredentials: true
|
||||||
'An unexpected error occurred while downloading. Please check your connection and try again.',
|
})
|
||||||
() => fetchObjectContent(id, type, fileName)
|
|
||||||
)
|
const notesData = response.data
|
||||||
}
|
logger.debug('Fetched notes:', notesData.length)
|
||||||
|
return notesData
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to fetch notes:', error)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,19 +628,24 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
apiServer: socketRef.current,
|
apiServer: socketRef.current,
|
||||||
error,
|
error,
|
||||||
connecting,
|
connecting,
|
||||||
|
connected,
|
||||||
lockObject,
|
lockObject,
|
||||||
unlockObject,
|
unlockObject,
|
||||||
fetchObjectLock,
|
fetchObjectLock,
|
||||||
updateObjectInfo,
|
updateObject,
|
||||||
createObject,
|
createObject,
|
||||||
onLockEvent,
|
deleteObject,
|
||||||
onUpdateEvent,
|
subscribeToObject,
|
||||||
|
subscribeToType,
|
||||||
|
subscribeToLock,
|
||||||
offUpdateEvent,
|
offUpdateEvent,
|
||||||
fetchObjectInfo,
|
fetchObject,
|
||||||
fetchTableData,
|
fetchObjects,
|
||||||
|
fetchObjectsByProperty,
|
||||||
fetchLoading,
|
fetchLoading,
|
||||||
showError,
|
showError,
|
||||||
fetchObjectContent
|
fetchObjectContent,
|
||||||
|
fetchNotes
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
|
|||||||
@ -53,8 +53,7 @@ const AuthProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (response.status === 200 && response.data) {
|
if (response.status === 200 && response.data) {
|
||||||
logger.debug('User is authenticated!')
|
logger.debug('Got auth token!')
|
||||||
setAuthenticated(true)
|
|
||||||
setToken(response.data.access_token)
|
setToken(response.data.access_token)
|
||||||
setExpiresAt(response.data.expires_at)
|
setExpiresAt(response.data.expires_at)
|
||||||
setUserProfile(response.data)
|
setUserProfile(response.data)
|
||||||
@ -89,75 +88,6 @@ const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const showTokenExpirationMessage = useCallback(
|
|
||||||
(expiresAt) => {
|
|
||||||
const now = new Date()
|
|
||||||
const expirationDate = new Date(expiresAt)
|
|
||||||
const timeRemaining = expirationDate - now
|
|
||||||
|
|
||||||
if (timeRemaining <= 0) {
|
|
||||||
if (authenticated) {
|
|
||||||
setShowSessionExpiredModal(true)
|
|
||||||
setAuthenticated(false)
|
|
||||||
notificationApi.destroy('token-expiration')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const minutes = Math.floor(timeRemaining / 60000)
|
|
||||||
const seconds = Math.floor((timeRemaining % 60000) / 1000)
|
|
||||||
|
|
||||||
// Only show notification in the final minute
|
|
||||||
if (minutes === 0) {
|
|
||||||
const totalSeconds = 60
|
|
||||||
const remainingSeconds = totalSeconds - seconds
|
|
||||||
const progress = (remainingSeconds / totalSeconds) * 100
|
|
||||||
|
|
||||||
notificationApi.info({
|
|
||||||
message: 'Session Expiring Soon',
|
|
||||||
description: (
|
|
||||||
<div>
|
|
||||||
<div style={{ marginBottom: 8 }}>
|
|
||||||
Your session will expire in {seconds} seconds
|
|
||||||
</div>
|
|
||||||
<Progress
|
|
||||||
percent={progress}
|
|
||||||
size='small'
|
|
||||||
status='active'
|
|
||||||
showInfo={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
duration: 0,
|
|
||||||
key: 'token-expiration',
|
|
||||||
icon: null,
|
|
||||||
placement: 'bottomRight',
|
|
||||||
style: {
|
|
||||||
width: 360
|
|
||||||
},
|
|
||||||
className: 'token-expiration-notification',
|
|
||||||
closeIcon: null,
|
|
||||||
onClose: () => {},
|
|
||||||
btn: (
|
|
||||||
<Button
|
|
||||||
type='primary'
|
|
||||||
size='small'
|
|
||||||
onClick={() => {
|
|
||||||
notificationApi.destroy('token-expiration')
|
|
||||||
refreshToken()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reload Session
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else if (minutes === 1) {
|
|
||||||
// Clear any existing notification when we enter the final minute
|
|
||||||
notificationApi.destroy('token-expiration')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[authenticated, notificationApi]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleSessionExpiredModalOk = () => {
|
const handleSessionExpiredModalOk = () => {
|
||||||
setShowSessionExpiredModal(false)
|
setShowSessionExpiredModal(false)
|
||||||
loginWithSSO()
|
loginWithSSO()
|
||||||
@ -167,22 +97,89 @@ const AuthProvider = ({ children }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let intervalId
|
let intervalId
|
||||||
|
|
||||||
const tokenRefreshInterval = () => {
|
const tokenRefresh = () => {
|
||||||
if (expiresAt) {
|
if (expiresAt) {
|
||||||
showTokenExpirationMessage(expiresAt)
|
const now = new Date()
|
||||||
|
const expirationDate = new Date(expiresAt)
|
||||||
|
const timeRemaining = expirationDate - now
|
||||||
|
|
||||||
|
if (timeRemaining <= 0) {
|
||||||
|
if (authenticated == true) {
|
||||||
|
setAuthenticated(false)
|
||||||
|
}
|
||||||
|
setShowSessionExpiredModal(true)
|
||||||
|
notificationApi.destroy('token-expiration')
|
||||||
|
} else {
|
||||||
|
if (authenticated == false) {
|
||||||
|
setAuthenticated(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const minutes = Math.floor(timeRemaining / 60000)
|
||||||
|
const seconds = Math.floor((timeRemaining % 60000) / 1000)
|
||||||
|
|
||||||
|
// Only show notification in the final minute
|
||||||
|
if (minutes === 0) {
|
||||||
|
const totalSeconds = 60
|
||||||
|
const remainingSeconds = totalSeconds - seconds
|
||||||
|
const progress = (remainingSeconds / totalSeconds) * 100
|
||||||
|
|
||||||
|
notificationApi.info({
|
||||||
|
message: 'Session Expiring Soon',
|
||||||
|
description: (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
Your session will expire in {seconds} seconds
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
percent={progress}
|
||||||
|
size='small'
|
||||||
|
status='active'
|
||||||
|
showInfo={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
duration: 0,
|
||||||
|
key: 'token-expiration',
|
||||||
|
icon: null,
|
||||||
|
placement: 'bottomRight',
|
||||||
|
style: {
|
||||||
|
width: 360
|
||||||
|
},
|
||||||
|
className: 'token-expiration-notification',
|
||||||
|
closeIcon: null,
|
||||||
|
onClose: () => {},
|
||||||
|
btn: (
|
||||||
|
<Button
|
||||||
|
type='primary'
|
||||||
|
size='small'
|
||||||
|
onClick={() => {
|
||||||
|
notificationApi.destroy('token-expiration')
|
||||||
|
refreshToken()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reload Session
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else if (minutes === 1) {
|
||||||
|
// Clear any existing notification when we enter the final minute
|
||||||
|
notificationApi.destroy('token-expiration')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authenticated) {
|
intervalId = setInterval(tokenRefresh, 1000)
|
||||||
intervalId = setInterval(tokenRefreshInterval, 1000)
|
|
||||||
}
|
console.log('fresh', authenticated)
|
||||||
|
tokenRefresh()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (intervalId) {
|
if (intervalId) {
|
||||||
clearInterval(intervalId)
|
clearInterval(intervalId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [expiresAt, authenticated, showTokenExpirationMessage])
|
}, [expiresAt, authenticated, notificationApi, refreshToken])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkAuthStatus()
|
checkAuthStatus()
|
||||||
|
|||||||
@ -461,16 +461,9 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
align='center'
|
align='center'
|
||||||
justify='space-between'
|
justify='space-between'
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex gap={'small'} align='center'>
|
||||||
gap={'small'}
|
{Icon ? <Icon style={{ fontSize: '20px' }} /> : null}
|
||||||
style={{ marginBottom: '2px' }}
|
|
||||||
align='center'
|
|
||||||
>
|
|
||||||
<Text>
|
|
||||||
{Icon ? (
|
|
||||||
<Icon style={{ fontSize: '20px' }} />
|
|
||||||
) : null}
|
|
||||||
</Text>
|
|
||||||
{item.name ? (
|
{item.name ? (
|
||||||
<Text ellipsis style={{ maxWidth: 170 }}>
|
<Text ellipsis style={{ maxWidth: 170 }}>
|
||||||
{item.name}
|
{item.name}
|
||||||
|
|||||||
@ -6,8 +6,16 @@ export const AuditLog = {
|
|||||||
prefix: 'ADL',
|
prefix: 'ADL',
|
||||||
icon: AuditLogIcon,
|
icon: AuditLogIcon,
|
||||||
actions: [],
|
actions: [],
|
||||||
columns: ['_id', 'owner', 'owner._id', 'parent._id', 'changes', 'createdAt'],
|
columns: [
|
||||||
filters: ['_id', 'owner._id', 'parent._id'],
|
'_id',
|
||||||
|
'owner',
|
||||||
|
'owner._id',
|
||||||
|
'parent._id',
|
||||||
|
'operation',
|
||||||
|
'changes',
|
||||||
|
'createdAt'
|
||||||
|
],
|
||||||
|
filters: ['_id', 'owner._id', 'parent._id', 'operation'],
|
||||||
sorters: ['createdAt'],
|
sorters: ['createdAt'],
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
@ -75,6 +83,12 @@ export const AuditLog = {
|
|||||||
showHyperlink: true,
|
showHyperlink: true,
|
||||||
showCopy: true
|
showCopy: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'operation',
|
||||||
|
label: 'Operation',
|
||||||
|
columnWidth: 120,
|
||||||
|
type: 'operation'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'changes',
|
name: 'changes',
|
||||||
label: 'Changes',
|
label: 'Changes',
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import JobIcon from '../../components/Icons/JobIcon'
|
import JobIcon from '../../components/Icons/JobIcon'
|
||||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||||
import EditIcon from '../../components/Icons/EditIcon'
|
|
||||||
|
|
||||||
export const Job = {
|
export const Job = {
|
||||||
name: 'job',
|
name: 'job',
|
||||||
@ -22,13 +21,6 @@ export const Job = {
|
|||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
icon: ReloadIcon,
|
icon: ReloadIcon,
|
||||||
url: (_id) => `/dashboard/production/jobs/info?jobId=${_id}&action=reload`
|
url: (_id) => `/dashboard/production/jobs/info?jobId=${_id}&action=reload`
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'edit',
|
|
||||||
label: 'Edit',
|
|
||||||
row: true,
|
|
||||||
icon: EditIcon,
|
|
||||||
url: (_id) => `/dashboard/production/jobs/info?jobId=${_id}&action=edit`
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
columns: [
|
columns: [
|
||||||
@ -68,7 +60,7 @@ export const Job = {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
columnFixed: 'left',
|
columnFixed: 'left',
|
||||||
objectType: 'gcodeFile',
|
objectType: 'gcodeFile',
|
||||||
readOnly: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'gcodeFile._id',
|
name: 'gcodeFile._id',
|
||||||
@ -98,9 +90,10 @@ export const Job = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'printers',
|
name: 'printers',
|
||||||
label: 'Assigned Printers',
|
label: 'Printers',
|
||||||
type: 'number',
|
type: 'objectList',
|
||||||
readOnly: true
|
objectType: 'printer',
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,5 +16,58 @@ export const Note = {
|
|||||||
url: (_id) => `/dashboard/management/notes/info?noteId=${_id}`
|
url: (_id) => `/dashboard/management/notes/info?noteId=${_id}`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
url: () => `#`
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'note',
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'noteType',
|
||||||
|
label: 'Note Type',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'noteType',
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parent._id',
|
||||||
|
label: 'Parent ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: (objectData) => {
|
||||||
|
return objectData.parentType
|
||||||
|
},
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'noteType._id',
|
||||||
|
label: 'Note Type ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'noteType'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'user._id',
|
||||||
|
label: 'User ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'user'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'content',
|
||||||
|
label: 'Content',
|
||||||
|
type: 'markdown'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ export const StockEvent = {
|
|||||||
label: 'Parent',
|
label: 'Parent',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
objectType: (objectData) => {
|
objectType: (objectData) => {
|
||||||
return objectData.parentType
|
return objectData?.parentType
|
||||||
},
|
},
|
||||||
value: null,
|
value: null,
|
||||||
showCopy: true
|
showCopy: true
|
||||||
@ -78,9 +78,12 @@ export const StockEvent = {
|
|||||||
{
|
{
|
||||||
name: 'value',
|
name: 'value',
|
||||||
label: 'Value',
|
label: 'Value',
|
||||||
columnWidth: 100,
|
columnWidth: 120,
|
||||||
type: 'number',
|
type: 'variance',
|
||||||
showCopy: true
|
showCopy: true,
|
||||||
|
suffix: (objectData) => {
|
||||||
|
return objectData.unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user