237 lines
6.7 KiB
JavaScript

import React, { useState, useEffect, useContext } from 'react'
import { useLocation } from 'react-router-dom'
import axios from 'axios'
import {
Descriptions,
Spin,
Space,
Button,
message,
Progress,
Typography,
Collapse
} from 'antd'
import { LoadingOutlined, CaretRightOutlined } from '@ant-design/icons'
import TimeDisplay from '../../common/TimeDisplay'
import JobState from '../../common/JobState'
import IdText from '../../common/IdText'
import SubJobsTree from '../../common/SubJobsTree'
import { SocketContext } from '../../context/SocketContext'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
import ReloadIcon from '../../../Icons/ReloadIcon'
import useCollapseState from '../../hooks/useCollapseState'
import config from '../../../../config'
const { Title } = Typography
const PrintJobInfo = () => {
const [printJobData, setPrintJobData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const location = useLocation()
const [messageApi] = message.useMessage()
const printJobId = new URLSearchParams(location.search).get('printJobId')
const { socket } = useContext(SocketContext)
const [collapseState, updateCollapseState] = useCollapseState(
'PrintJobInfo',
{
info: true,
subJobs: true
}
)
useEffect(() => {
if (printJobId) {
fetchPrintJobDetails()
}
}, [printJobId])
useEffect(() => {
if (socket && printJobId) {
socket.on('notify_job_update', (updateData) => {
if (updateData._id === printJobId) {
setPrintJobData((prevData) => {
if (!prevData) return prevData
return {
...prevData,
state: updateData.state,
...updateData
}
})
}
})
}
return () => {
if (socket) {
socket.off('notify_job_update')
}
}
}, [socket, printJobId])
const fetchPrintJobDetails = async () => {
try {
setLoading(true)
const response = await axios.get(
`${config.backendUrl}/printjobs/${printJobId}`,
{
headers: {
Accept: 'application/json'
},
withCredentials: true // Important for including cookies
}
)
setPrintJobData(response.data)
setError(null)
} catch (err) {
setError('Failed to fetch print job details')
messageApi.error('Failed to fetch print job details')
} finally {
setLoading(false)
}
}
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '20px' }}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
</div>
)
}
if (error || !printJobData) {
return (
<Space
direction='vertical'
style={{ width: '100%', textAlign: 'center' }}
>
<p>{error || 'Print job not found'}</p>
<Button icon={<ReloadIcon />} onClick={fetchPrintJobDetails}>
Retry
</Button>
</Space>
)
}
return (
<div style={{ height: '100%', minHeight: 0, overflowY: 'auto' }}>
<Collapse
ghost
collapsible='icon'
activeKey={collapseState.info ? ['1'] : []}
onChange={(keys) => updateCollapseState('info', keys.length > 0)}
expandIcon={({ isActive }) => (
<CaretRightOutlined
rotate={isActive ? 90 : 0}
style={{ paddingTop: '2px' }}
/>
)}
className='no-h-padding-collapse no-t-padding-collapse'
>
<Collapse.Panel
header={
<Title level={5} style={{ margin: 0 }}>
Print Job Information
</Title>
}
key='1'
>
<Descriptions
bordered
column={{
xs: 1,
sm: 1,
md: 1,
lg: 2,
xl: 2,
xxl: 2
}}
>
<Descriptions.Item label='ID'>
<IdText id={printJobData._id} type={'job'} />
</Descriptions.Item>
<Descriptions.Item label='Status'>
<JobState
job={printJobData}
showProgress={false}
showQuantity={false}
showId={false}
/>
</Descriptions.Item>
<Descriptions.Item label='GCode File Name'>
<Space>
<GCodeFileIcon />
{printJobData.gcodeFile?.name || 'Not specified'}
</Space>
</Descriptions.Item>
<Descriptions.Item label='GCode File ID'>
<IdText
id={printJobData.gcodeFile.id}
type={'gcodeFile'}
showHyperlink={true}
/>
</Descriptions.Item>
<Descriptions.Item label='Quantity'>
{printJobData.quantity || 1}
</Descriptions.Item>
<Descriptions.Item label='Created At'>
<TimeDisplay dateTime={printJobData.createdAt} showSince={true} />
</Descriptions.Item>
<Descriptions.Item label='Started At'>
{printJobData.startedAt ? (
<TimeDisplay
dateTime={printJobData.startedAt}
showSince={true}
/>
) : (
'n/a'
)}
</Descriptions.Item>
{printJobData.state.type === 'printing' && (
<Descriptions.Item label='Progress'>
<Progress
percent={Math.round((printJobData.state.progress || 0) * 100)}
/>
</Descriptions.Item>
)}
<Descriptions.Item label='Assigned Printers'>
{printJobData.printers?.length > 0 ? (
<span>{printJobData.printers.length} printers assigned</span>
) : (
'Any available printer'
)}
</Descriptions.Item>
</Descriptions>
</Collapse.Panel>
</Collapse>
<Collapse
ghost
collapsible='icon'
activeKey={collapseState.subJobs ? ['2'] : []}
onChange={(keys) => updateCollapseState('subJobs', keys.length > 0)}
expandIcon={({ isActive }) => (
<CaretRightOutlined
rotate={isActive ? 90 : 0}
style={{ paddingTop: '2px' }}
/>
)}
className='no-h-padding-collapse'
>
<Collapse.Panel
header={
<Title level={5} style={{ margin: 0 }}>
Sub Job Information
</Title>
}
key='2'
>
<SubJobsTree printJobData={printJobData} />
</Collapse.Panel>
</Collapse>
</div>
)
}
export default PrintJobInfo