202 lines
5.4 KiB
JavaScript
202 lines
5.4 KiB
JavaScript
import PropTypes from 'prop-types'
|
|
import { Card, Tree, Spin, Space, Button, message } from 'antd'
|
|
import { LoadingOutlined } from '@ant-design/icons'
|
|
import React, { useState, useEffect, useContext } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import SubJobState from './SubJobState'
|
|
import { SocketContext } from '../context/SocketContext'
|
|
import axios from 'axios'
|
|
import JobState from './JobState'
|
|
import JobIcon from '../../Icons/JobIcon'
|
|
import SubJobIcon from '../../Icons/SubJobIcon'
|
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
|
|
|
import config from '../../../config'
|
|
|
|
const PrinterJobsTree = ({
|
|
subJobs: initialSubJobs,
|
|
loading: initialLoading
|
|
}) => {
|
|
const [subJobs, setSubJobs] = useState(initialSubJobs || [])
|
|
const [treeLoading, setTreeLoading] = useState(initialLoading)
|
|
const [error, setError] = useState(null)
|
|
const { socket } = useContext(SocketContext)
|
|
const [messageApi] = message.useMessage()
|
|
const [expandedKeys, setExpandedKeys] = useState([])
|
|
const [treeData, setTreeData] = useState([])
|
|
const navigate = useNavigate()
|
|
|
|
const handleNodeClick = (selectedKeys) => {
|
|
const key = selectedKeys[0]
|
|
if (key.startsWith('job-')) {
|
|
const jobId = key.replace('job-', '')
|
|
navigate(`/dashboard/production/jobs/info?jobId=${jobId}`)
|
|
}
|
|
}
|
|
|
|
const buildTreeData = (subJobsData) => {
|
|
if (!subJobsData?.length) {
|
|
setTreeData([])
|
|
setExpandedKeys([])
|
|
return
|
|
}
|
|
|
|
// Group subjobs by job
|
|
const jobGroups = subJobsData.reduce((acc, subJob) => {
|
|
const jobId = subJob.job._id
|
|
if (!acc[jobId]) {
|
|
acc[jobId] = {
|
|
job: subJob.job,
|
|
subJobs: []
|
|
}
|
|
}
|
|
acc[jobId].subJobs.push(subJob)
|
|
return acc
|
|
}, {})
|
|
|
|
// Create tree nodes for each job
|
|
const jobNodes = Object.values(jobGroups).map(({ job, subJobs }) => {
|
|
setExpandedKeys((prev) => [...prev, `job-${job._id}`])
|
|
return {
|
|
title: (
|
|
<Space size={5}>
|
|
<JobIcon />
|
|
{'Job'}
|
|
<JobState job={job} />
|
|
</Space>
|
|
),
|
|
key: `job-${job._id}`,
|
|
children: subJobs.map((subJob) => ({
|
|
title: (
|
|
<Space>
|
|
<SubJobIcon />
|
|
{'Sub Job'}
|
|
<SubJobState
|
|
subJob={subJob}
|
|
showProgress={false}
|
|
showControls={false}
|
|
/>
|
|
</Space>
|
|
),
|
|
key: `subjob-${subJob._id}`,
|
|
isLeaf: true
|
|
}))
|
|
}
|
|
})
|
|
|
|
setTreeData(jobNodes)
|
|
}
|
|
|
|
useEffect(() => {
|
|
buildTreeData(subJobs)
|
|
}, [subJobs])
|
|
|
|
useEffect(() => {
|
|
const initializeData = async () => {
|
|
if (!initialSubJobs) {
|
|
try {
|
|
setTreeLoading(true)
|
|
const response = await axios.get(`${config.backendUrl}/jobs`, {
|
|
headers: { Accept: 'application/json' },
|
|
withCredentials: true
|
|
})
|
|
if (response.data?.subJobs) {
|
|
setSubJobs(response.data.subJobs)
|
|
}
|
|
} catch (err) {
|
|
setError('Failed to fetch sub jobs')
|
|
messageApi.error('Failed to fetch sub jobs')
|
|
} finally {
|
|
setTreeLoading(false)
|
|
}
|
|
} else {
|
|
setSubJobs(initialSubJobs)
|
|
}
|
|
}
|
|
|
|
initializeData()
|
|
|
|
// Add socket.io event listener for subjob updates
|
|
if (socket) {
|
|
socket.on('notify_subjob_update', (updateData) => {
|
|
if (updateData.subJobId) {
|
|
setSubJobs((prevSubJobs) =>
|
|
prevSubJobs.map((subJob) => {
|
|
if (subJob._id === updateData._id) {
|
|
return {
|
|
...subJob,
|
|
state: updateData.state,
|
|
subJobId: updateData.subJobId
|
|
}
|
|
}
|
|
return subJob
|
|
})
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
return () => {
|
|
if (socket) {
|
|
socket.off('notify_subjob_update')
|
|
}
|
|
}
|
|
}, [initialSubJobs, socket])
|
|
|
|
if (error) {
|
|
return (
|
|
<Space
|
|
direction='vertical'
|
|
style={{ width: '100%', textAlign: 'center' }}
|
|
>
|
|
<p>{error}</p>
|
|
<Button icon={<ReloadIcon />} onClick={() => setError(null)}>
|
|
Retry
|
|
</Button>
|
|
</Space>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Spin indicator={<LoadingOutlined />} spinning={treeLoading}>
|
|
<Card>
|
|
<Tree
|
|
treeData={treeData}
|
|
expandedKeys={expandedKeys}
|
|
onExpand={setExpandedKeys}
|
|
onSelect={handleNodeClick}
|
|
showLine={true}
|
|
/>
|
|
</Card>
|
|
</Spin>
|
|
)
|
|
}
|
|
|
|
PrinterJobsTree.propTypes = {
|
|
subJobs: PropTypes.arrayOf(
|
|
PropTypes.shape({
|
|
state: PropTypes.object.isRequired,
|
|
_id: PropTypes.string.isRequired,
|
|
printer: PropTypes.string.isRequired,
|
|
job: PropTypes.shape({
|
|
state: PropTypes.object.isRequired,
|
|
_id: PropTypes.string.isRequired,
|
|
printers: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
createdAt: PropTypes.string.isRequired,
|
|
updatedAt: PropTypes.string.isRequired,
|
|
startedAt: PropTypes.string.isRequired,
|
|
gcodeFile: PropTypes.string.isRequired,
|
|
quantity: PropTypes.number.isRequired,
|
|
subJobs: PropTypes.arrayOf(PropTypes.string).isRequired
|
|
}).isRequired,
|
|
subJobId: PropTypes.string.isRequired,
|
|
number: PropTypes.number.isRequired,
|
|
createdAt: PropTypes.string.isRequired,
|
|
updatedAt: PropTypes.string.isRequired
|
|
})
|
|
),
|
|
loading: PropTypes.bool
|
|
}
|
|
|
|
export default PrinterJobsTree
|