Implement Cancel Sub Job Feature and Enhance Printer Queue Management
Some checks reported errors
farmcontrol/farmcontrol-ui/pipeline/head Something is wrong with the build of this commit
Some checks reported errors
farmcontrol/farmcontrol-ui/pipeline/head Something is wrong with the build of this commit
- Added CancelSubJob component for handling sub job cancellation with user confirmation. - Updated PrinterQueue and PrinterQueueItem components to support cancellation actions and display appropriate controls based on user permissions. - Enhanced ObjectDisplay component to trigger data updates via onData callback, improving state management. - Introduced new styles for printer queue steps in App.css to improve layout consistency.
This commit is contained in:
parent
9b86114d99
commit
ae8d4532c0
@ -4397,6 +4397,13 @@ body.objectKanbanColumnResizing * {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.printerQueue.ant-steps .ant-steps-item-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
.mobile-footer-navigation.ant-layout-footer {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
|
||||
@ -465,6 +465,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
|
||||
currentSubJob={
|
||||
objectFormState.objectData?.currentSubJob
|
||||
}
|
||||
printerId={printerId}
|
||||
showHyperlink={!slicerIntegration}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
69
src/components/Dashboard/Production/SubJobs/CancelSubJob.jsx
Normal file
69
src/components/Dashboard/Production/SubJobs/CancelSubJob.jsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { useState, useContext } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ApiServerContext } from '../../context/ApiServerContext'
|
||||
import { message } from 'antd'
|
||||
import MessageDialogView from '../../common/MessageDialogView.jsx'
|
||||
|
||||
const getRefId = (value) => {
|
||||
if (!value) return null
|
||||
if (typeof value === 'string') return value
|
||||
if (typeof value._id === 'string') return value._id
|
||||
if (typeof value._id === 'object' && value._id?._id) return value._id._id
|
||||
return null
|
||||
}
|
||||
|
||||
const CancelSubJob = ({ onOk, objectData }) => {
|
||||
const [cancelLoading, setCancelLoading] = useState(false)
|
||||
const { connected, sendObjectAction } = useContext(ApiServerContext)
|
||||
|
||||
const handleCancel = async () => {
|
||||
if (!connected) {
|
||||
message.error('Not connected to server')
|
||||
return
|
||||
}
|
||||
|
||||
const printerId = getRefId(objectData?.printer)
|
||||
const subJobId = getRefId(objectData)
|
||||
if (!printerId || !subJobId) {
|
||||
message.error('Sub job is not assigned to a printer')
|
||||
return
|
||||
}
|
||||
|
||||
setCancelLoading(true)
|
||||
try {
|
||||
sendObjectAction(printerId, 'printer', {
|
||||
type: 'cancelSubJob',
|
||||
data: {
|
||||
_id: subJobId
|
||||
}
|
||||
})
|
||||
message.success('Sub job cancel initiated')
|
||||
onOk()
|
||||
} catch (error) {
|
||||
console.error('Error cancelling sub job:', error)
|
||||
} finally {
|
||||
setCancelLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const subJobLabel =
|
||||
objectData?.name || objectData?._reference || objectData?._id
|
||||
|
||||
return (
|
||||
<MessageDialogView
|
||||
title='Are you sure you want to cancel this sub job?'
|
||||
description={`Cancelling sub job ${subJobLabel} will remove it from the printer queue.`}
|
||||
onOk={handleCancel}
|
||||
okText='Cancel Sub Job'
|
||||
okLoading={cancelLoading}
|
||||
danger
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
CancelSubJob.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default CancelSubJob
|
||||
@ -19,7 +19,8 @@ const ObjectDisplay = ({
|
||||
object,
|
||||
objectType,
|
||||
showHyperlink = false,
|
||||
showSpotlight = true
|
||||
showSpotlight = true,
|
||||
onData
|
||||
}) => {
|
||||
const [objectData, setObjectData] = useState(object)
|
||||
const [isHydrating, setIsHydrating] = useState(false)
|
||||
@ -28,12 +29,18 @@ const ObjectDisplay = ({
|
||||
const { token } = useContext(AuthContext)
|
||||
const { callAction } = useActions()
|
||||
const idRef = useRef(null)
|
||||
const onDataRef = useRef(onData)
|
||||
onDataRef.current = onData
|
||||
|
||||
// Update event handler
|
||||
const updateObjectEventHandler = useCallback((value) => {
|
||||
setObjectData((prev) => merge({}, prev, value))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
onDataRef.current?.(objectData)
|
||||
}, [objectData])
|
||||
|
||||
// Ensure ID is valid before hydrate/subscribe (non-empty, not null/undefined)
|
||||
const isValidId = useCallback((id) => {
|
||||
return id != null && String(id).trim() !== ''
|
||||
@ -271,7 +278,8 @@ ObjectDisplay.propTypes = {
|
||||
objectType: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
showHyperlink: PropTypes.bool,
|
||||
showSpotlight: PropTypes.bool
|
||||
showSpotlight: PropTypes.bool,
|
||||
onData: PropTypes.func
|
||||
}
|
||||
|
||||
export default ObjectDisplay
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
import { createElement, useContext, useEffect, useRef, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button, Flex, Steps } from 'antd'
|
||||
import ObjectDisplay from './ObjectDisplay'
|
||||
import { Steps } from 'antd'
|
||||
import MissingPlaceholder from './MissingPlaceholder'
|
||||
import Tooltip from './Tooltip'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import { findAction } from '../../../utils/modelActions'
|
||||
import { hasActionPermission } from '../../../database/permissions'
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
import { useActions } from '../context/ActionsContext'
|
||||
|
||||
const getId = (item) => {
|
||||
if (!item) return null
|
||||
@ -17,7 +24,96 @@ const toObject = (item) => {
|
||||
return item
|
||||
}
|
||||
|
||||
const PrinterQueue = ({ queue, currentSubJob, showHyperlink = true }) => {
|
||||
const canCancelSubJob = (cancelAction, objectData, userProfile, model) => {
|
||||
if (!cancelAction || !objectData) return false
|
||||
if (!hasActionPermission(userProfile, model, cancelAction.name)) return false
|
||||
if (typeof cancelAction.visible === 'function') {
|
||||
if (!cancelAction.visible(objectData)) return false
|
||||
} else if (cancelAction.visible === false) {
|
||||
return false
|
||||
}
|
||||
if (typeof cancelAction.disabled === 'function') {
|
||||
return !cancelAction.disabled({ ...objectData, _user: userProfile })
|
||||
}
|
||||
return !cancelAction.disabled
|
||||
}
|
||||
|
||||
const PrinterQueueItem = ({ item, showHyperlink, printerId }) => {
|
||||
const itemId = getId(item)
|
||||
const [objectData, setObjectData] = useState(item)
|
||||
const itemRef = useRef(item)
|
||||
itemRef.current = item
|
||||
const { userProfile } = useContext(AuthContext)
|
||||
const { callAction } = useActions()
|
||||
const model = getModelByName('subJob')
|
||||
const cancelAction = findAction(model, 'cancel')
|
||||
|
||||
useEffect(() => {
|
||||
setObjectData(itemRef.current)
|
||||
}, [itemId])
|
||||
|
||||
const showCancel = canCancelSubJob(
|
||||
cancelAction,
|
||||
objectData,
|
||||
userProfile,
|
||||
model
|
||||
)
|
||||
|
||||
const handleCancel = (event) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
if (!showCancel || !itemId) return
|
||||
callAction(
|
||||
cancelAction,
|
||||
{
|
||||
...objectData,
|
||||
_id: itemId,
|
||||
printer: objectData?.printer || printerId,
|
||||
_user: userProfile
|
||||
},
|
||||
'subJob'
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex align='center' gap={4} style={{ width: '100%', minWidth: 0 }}>
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<ObjectDisplay
|
||||
object={item}
|
||||
objectType='subJob'
|
||||
showHyperlink={showHyperlink}
|
||||
onData={setObjectData}
|
||||
/>
|
||||
</div>
|
||||
{showCancel ? (
|
||||
<Tooltip title={cancelAction.label}>
|
||||
<Button
|
||||
icon={
|
||||
cancelAction.icon ? createElement(cancelAction.icon) : undefined
|
||||
}
|
||||
type='text'
|
||||
size='small'
|
||||
danger={cancelAction.danger || false}
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
PrinterQueueItem.propTypes = {
|
||||
item: PropTypes.object,
|
||||
showHyperlink: PropTypes.bool,
|
||||
printerId: PropTypes.string
|
||||
}
|
||||
|
||||
const PrinterQueue = ({
|
||||
queue,
|
||||
currentSubJob,
|
||||
showHyperlink = true,
|
||||
printerId
|
||||
}) => {
|
||||
const queueItems = Array.isArray(queue)
|
||||
? queue.map(toObject).filter(Boolean)
|
||||
: []
|
||||
@ -53,10 +149,11 @@ const PrinterQueue = ({ queue, currentSubJob, showHyperlink = true }) => {
|
||||
size='small'
|
||||
items={items.map((item) => ({
|
||||
title: (
|
||||
<ObjectDisplay
|
||||
object={item}
|
||||
objectType='subJob'
|
||||
<PrinterQueueItem
|
||||
key={getId(item)}
|
||||
item={item}
|
||||
showHyperlink={showHyperlink}
|
||||
printerId={printerId}
|
||||
/>
|
||||
)
|
||||
}))}
|
||||
@ -67,7 +164,8 @@ const PrinterQueue = ({ queue, currentSubJob, showHyperlink = true }) => {
|
||||
PrinterQueue.propTypes = {
|
||||
queue: PropTypes.array,
|
||||
currentSubJob: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
showHyperlink: PropTypes.bool
|
||||
showHyperlink: PropTypes.bool,
|
||||
printerId: PropTypes.string
|
||||
}
|
||||
|
||||
export default PrinterQueue
|
||||
|
||||
@ -3,6 +3,9 @@ import { createElement, lazy } from 'react'
|
||||
const SubJobInfo = lazy(
|
||||
() => import('../../components/Dashboard/Production/SubJobs/SubJobInfo')
|
||||
)
|
||||
const CancelSubJob = lazy(
|
||||
() => import('../../components/Dashboard/Production/SubJobs/CancelSubJob')
|
||||
)
|
||||
import SubJobIcon from '../../components/Icons/SubJobIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
@ -35,13 +38,18 @@ export const SubJob = {
|
||||
},
|
||||
{
|
||||
name: 'cancel',
|
||||
type: 'page',
|
||||
pageName: 'info',
|
||||
type: 'modal',
|
||||
modalWidth: 520,
|
||||
modalCentered: true,
|
||||
label: 'Cancel Sub Job',
|
||||
row: true,
|
||||
danger: true,
|
||||
icon: XMarkIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type !== 'queued'
|
||||
},
|
||||
content: (objectData, { onOk } = {}) => {
|
||||
return createElement(CancelSubJob, { objectData, onOk })
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user