Compare commits
26 Commits
c2f55a5967
...
c6c99bb02c
| Author | SHA1 | Date | |
|---|---|---|---|
| c6c99bb02c | |||
| 91e7121fd5 | |||
| afe25c1e09 | |||
| 8d63b9a84d | |||
| 808d45273d | |||
| 64c4d25982 | |||
| 7ac56cc69c | |||
| e4b8f52e6b | |||
| e114a45348 | |||
| b7bb6121b7 | |||
| dabad4f489 | |||
| 94c55c8e77 | |||
| 8f67d7daae | |||
| e788eaba91 | |||
| 6af832afe5 | |||
| 69c387ca1d | |||
| ecf73c13c0 | |||
| 657d6a5c6e | |||
| 8f34c262a0 | |||
| 5cb586246f | |||
| b12d230a8e | |||
| aeebaaddda | |||
| 8ed0287d73 | |||
| 030053b965 | |||
| 2d2df403e3 | |||
| 8c622420bb |
@ -55,7 +55,9 @@
|
|||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
.electron-navigation-wrapper li, .electron-navigation-wrapper button, .electron-navigation-wrapper .ant-tag{
|
.electron-navigation-wrapper li,
|
||||||
|
.electron-navigation-wrapper button,
|
||||||
|
.electron-navigation-wrapper .ant-tag {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +69,8 @@
|
|||||||
padding-inline: 10px;
|
padding-inline: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.electron-sidebar .ant-menu-item, .electron-sidebar .ant-menu-submenu-title {
|
.electron-sidebar .ant-menu-item,
|
||||||
|
.electron-sidebar .ant-menu-submenu-title {
|
||||||
height: 32.5px !important;
|
height: 32.5px !important;
|
||||||
line-height: 32.5px !important;
|
line-height: 32.5px !important;
|
||||||
}
|
}
|
||||||
@ -81,7 +84,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--unit-100vh: 100vh;
|
--unit-100vh: 100vh;
|
||||||
}
|
}
|
||||||
@ -229,15 +231,17 @@ body {
|
|||||||
table-layout: fixed !important;
|
table-layout: fixed !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.objectTableDescritions >.ant-descriptions-view .ant-descriptions-row >.ant-descriptions-item-label {
|
.objectTableDescritions
|
||||||
|
> .ant-descriptions-view
|
||||||
|
.ant-descriptions-row
|
||||||
|
> .ant-descriptions-item-label {
|
||||||
width: 35%;
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.farmcontrol-splitter > .ant-splitter-bar {
|
.farmcontrol-splitter > .ant-splitter-bar {
|
||||||
margin: 8px
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.dark-mode {
|
.dark-mode {
|
||||||
--sb-track-color: #1d1d1d;
|
--sb-track-color: #1d1d1d;
|
||||||
--sb-thumb-color: #848484;
|
--sb-thumb-color: #848484;
|
||||||
@ -249,7 +253,6 @@ body {
|
|||||||
--sb-size: 8px;
|
--sb-size: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
@ -307,3 +310,7 @@ body {
|
|||||||
.ant-table-body {
|
.ant-table-body {
|
||||||
scrollbar-color: auto;
|
scrollbar-color: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-select-selection-item .ant-tag {
|
||||||
|
margin-left: 1px !important;
|
||||||
|
}
|
||||||
|
|||||||
855
package-lock.json
generated
855
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,27 +1,14 @@
|
|||||||
import { useState, useContext, useEffect } from 'react'
|
import { useState, useContext, useEffect } from 'react'
|
||||||
import {
|
import { Form, Flex, Descriptions, Alert } from 'antd'
|
||||||
Form,
|
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
Flex,
|
|
||||||
Steps,
|
|
||||||
Divider,
|
|
||||||
Descriptions,
|
|
||||||
Alert
|
|
||||||
} from 'antd'
|
|
||||||
import { useMediaQuery } from 'react-responsive'
|
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { PrintServerContext } from '../../context/PrintServerContext'
|
|
||||||
|
|
||||||
import FilamentStockSelect from '../../common/FilamentStockSelect'
|
|
||||||
import PrinterSelect from '../../common/PrinterSelect'
|
|
||||||
import FilamentStockDisplay from '../../common/FilamentStockDisplay'
|
|
||||||
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
|
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
|
||||||
|
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import PrinterState from '../../common/StateDisplay'
|
import ObjectSelect from '../../common/ObjectSelect'
|
||||||
|
import ObjectDisplay from '../../common/ObjectDisplay'
|
||||||
const { Title } = Typography
|
import WizardView from '../../common/WizardView'
|
||||||
|
import { ApiServerContext } from '../../context/ApiServerContext'
|
||||||
|
|
||||||
const LoadFilamentStock = ({
|
const LoadFilamentStock = ({
|
||||||
onOk,
|
onOk,
|
||||||
@ -29,7 +16,8 @@ const LoadFilamentStock = ({
|
|||||||
printer = null,
|
printer = null,
|
||||||
filamentStockLoaded = false
|
filamentStockLoaded = false
|
||||||
}) => {
|
}) => {
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const { connected, subscribeToObjectEvent, sendObjectAction } =
|
||||||
|
useContext(ApiServerContext)
|
||||||
|
|
||||||
LoadFilamentStock.propTypes = {
|
LoadFilamentStock.propTypes = {
|
||||||
onOk: PropTypes.func.isRequired,
|
onOk: PropTypes.func.isRequired,
|
||||||
@ -38,8 +26,6 @@ const LoadFilamentStock = ({
|
|||||||
filamentStockLoaded: PropTypes.bool
|
filamentStockLoaded: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const { printServer } = useContext(PrintServerContext)
|
|
||||||
|
|
||||||
const initialLoadFilamentStockForm = {
|
const initialLoadFilamentStockForm = {
|
||||||
printer: printer,
|
printer: printer,
|
||||||
filamentStock: null
|
filamentStock: null
|
||||||
@ -47,8 +33,7 @@ const LoadFilamentStock = ({
|
|||||||
|
|
||||||
const [loadFilamentStockLoading, setLoadFilamentStockLoading] =
|
const [loadFilamentStockLoading, setLoadFilamentStockLoading] =
|
||||||
useState(false)
|
useState(false)
|
||||||
const [currentStep, setCurrentStep] = useState(0)
|
const [formValid, setFormValid] = useState(false)
|
||||||
const [nextEnabled, setNextEnabled] = useState(false)
|
|
||||||
const [currentTemperature, setCurrentTemperature] = useState(-1)
|
const [currentTemperature, setCurrentTemperature] = useState(-1)
|
||||||
const [targetTemperature, setTargetTemperature] = useState(0)
|
const [targetTemperature, setTargetTemperature] = useState(0)
|
||||||
const [filamentSensorDetected, setFilamentSensorDetected] =
|
const [filamentSensorDetected, setFilamentSensorDetected] =
|
||||||
@ -62,77 +47,69 @@ const LoadFilamentStock = ({
|
|||||||
loadFilamentStockForm
|
loadFilamentStockForm
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add websocket temperature monitoring
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loadFilamentStockFormValues.printer) {
|
if (printer?._id && connected) {
|
||||||
const params = {
|
const temperatureEventUnsubscribe = subscribeToObjectEvent(
|
||||||
printerId: loadFilamentStockFormValues.printer._id,
|
printer._id,
|
||||||
objects: {
|
'printer',
|
||||||
extruder: null,
|
'temperature',
|
||||||
'filament_switch_sensor fsensor': null
|
(event) => {
|
||||||
|
if (event.data?.extruder?.current) {
|
||||||
|
setCurrentTemperature(event.data.extruder.current)
|
||||||
|
}
|
||||||
|
if (event.data?.extruder?.target) {
|
||||||
|
setTargetTemperature(event.data.extruder.target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyStatusUpdate = (statusUpdate) => {
|
|
||||||
if (statusUpdate?.extruder?.temperature !== undefined) {
|
|
||||||
setCurrentTemperature(statusUpdate.extruder.temperature)
|
|
||||||
}
|
|
||||||
if (statusUpdate?.extruder?.target !== undefined) {
|
|
||||||
setTargetTemperature(statusUpdate.extruder.target)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
statusUpdate?.['filament_switch_sensor fsensor']
|
|
||||||
?.filament_detected !== undefined
|
|
||||||
) {
|
|
||||||
setFilamentSensorDetected(
|
|
||||||
Boolean(
|
|
||||||
statusUpdate['filament_switch_sensor fsensor'].filament_detected
|
|
||||||
)
|
)
|
||||||
|
const filamentStockEventUnsubscribe = subscribeToObjectEvent(
|
||||||
|
printer._id,
|
||||||
|
'printer',
|
||||||
|
'filamentSensor',
|
||||||
|
(event) => {
|
||||||
|
console.log('filamentSensor', event.data)
|
||||||
|
setFilamentSensorDetected(event.data.detected)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printServer.emit('printer.objects.subscribe', params)
|
|
||||||
printServer.emit('printer.objects.query', params)
|
|
||||||
printServer.on('notify_status_update', notifyStatusUpdate)
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
printServer.off('notify_status_update', notifyStatusUpdate)
|
if (temperatureEventUnsubscribe) temperatureEventUnsubscribe()
|
||||||
printServer.emit('printer.objects.unsubscribe', params)
|
if (filamentStockEventUnsubscribe) filamentStockEventUnsubscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [printServer, loadFilamentStockFormValues.printer])
|
}, [printer?._id, connected])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Validate form fields
|
||||||
loadFilamentStockForm
|
loadFilamentStockForm
|
||||||
.validateFields({
|
.validateFields({
|
||||||
validateOnly: true
|
validateOnly: true
|
||||||
})
|
})
|
||||||
.then(() => setNextEnabled(filamentSensorDetected))
|
.then(() => {
|
||||||
.catch(() => setNextEnabled(false))
|
const hasPrinter = Boolean(loadFilamentStockFormValues.printer)
|
||||||
|
const hasFilamentStock = Boolean(
|
||||||
|
loadFilamentStockFormValues.filamentStock
|
||||||
|
)
|
||||||
|
|
||||||
|
// Step 0 (Preheat): needs printer + filament detected + (temp reached or no temp set)
|
||||||
|
const preheatReady =
|
||||||
|
hasPrinter &&
|
||||||
|
filamentSensorDetected &&
|
||||||
|
(targetTemperature === 0 || currentTemperature >= targetTemperature)
|
||||||
|
|
||||||
|
// Step 1+ (Required/Summary): needs filamentStock selected
|
||||||
|
// Form is valid if preheat is ready (for step 0) OR filamentStock is selected (for step 1+)
|
||||||
|
// This allows progression: step 0 can proceed when preheat is ready,
|
||||||
|
// and step 1+ can proceed when filamentStock is selected
|
||||||
|
setFormValid(preheatReady || (hasPrinter && hasFilamentStock))
|
||||||
|
})
|
||||||
|
.catch(() => setFormValid(false))
|
||||||
}, [
|
}, [
|
||||||
loadFilamentStockForm,
|
loadFilamentStockForm,
|
||||||
loadFilamentStockFormUpdateValues,
|
loadFilamentStockFormUpdateValues,
|
||||||
filamentSensorDetected
|
loadFilamentStockFormValues,
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
filamentSensorDetected == true &&
|
|
||||||
currentTemperature >= targetTemperature
|
|
||||||
) {
|
|
||||||
setNextEnabled(filamentSensorDetected)
|
|
||||||
if (currentStep == 0) {
|
|
||||||
setCurrentStep(1)
|
|
||||||
}
|
|
||||||
} else if (filamentSensorDetected == false) {
|
|
||||||
setCurrentStep(0)
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
filamentSensorDetected,
|
filamentSensorDetected,
|
||||||
targetTemperature,
|
|
||||||
currentTemperature,
|
currentTemperature,
|
||||||
currentStep
|
targetTemperature
|
||||||
])
|
])
|
||||||
|
|
||||||
const summaryItems = [
|
const summaryItems = [
|
||||||
@ -140,8 +117,9 @@ const LoadFilamentStock = ({
|
|||||||
key: 'filamentStock',
|
key: 'filamentStock',
|
||||||
label: 'Stock',
|
label: 'Stock',
|
||||||
children: loadFilamentStockFormValues.filamentStock ? (
|
children: loadFilamentStockFormValues.filamentStock ? (
|
||||||
<FilamentStockDisplay
|
<ObjectDisplay
|
||||||
filamentStock={loadFilamentStockFormValues.filamentStock}
|
objectType='filamentStock'
|
||||||
|
object={loadFilamentStockFormValues.filamentStock}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
'n/a'
|
'n/a'
|
||||||
@ -151,9 +129,12 @@ const LoadFilamentStock = ({
|
|||||||
key: 'printer',
|
key: 'printer',
|
||||||
label: 'Printer',
|
label: 'Printer',
|
||||||
children: loadFilamentStockFormValues.printer ? (
|
children: loadFilamentStockFormValues.printer ? (
|
||||||
<PrinterState printer={loadFilamentStockFormValues.printer} />
|
<ObjectDisplay
|
||||||
|
objectType='printer'
|
||||||
|
object={loadFilamentStockFormValues.printer}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
'n/a>'
|
'n/a'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -169,10 +150,16 @@ const LoadFilamentStock = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Set the extruder temperature
|
// Set the extruder temperature
|
||||||
await printServer.emit('printer.filamentstock.load', {
|
await sendObjectAction(
|
||||||
printerId: loadFilamentStockFormValues.printer._id,
|
loadFilamentStockFormValues.printer._id,
|
||||||
filamentStockId: loadFilamentStockFormValues.filamentStock._id
|
'printer',
|
||||||
})
|
{
|
||||||
|
type: 'loadFilamentStock',
|
||||||
|
data: {
|
||||||
|
filamentStock: loadFilamentStockFormValues.filamentStock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
onOk()
|
onOk()
|
||||||
} finally {
|
} finally {
|
||||||
setLoadFilamentStockLoading(false)
|
setLoadFilamentStockLoading(false)
|
||||||
@ -196,7 +183,7 @@ const LoadFilamentStock = ({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<PrinterSelect checkable={false} />
|
<ObjectSelect type='printer' checkable={false} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{targetTemperature == 0 ? (
|
{targetTemperature == 0 ? (
|
||||||
<Alert
|
<Alert
|
||||||
@ -226,10 +213,9 @@ const LoadFilamentStock = ({
|
|||||||
|
|
||||||
{loadFilamentStockFormValues.printer ? (
|
{loadFilamentStockFormValues.printer ? (
|
||||||
<PrinterTemperaturePanel
|
<PrinterTemperaturePanel
|
||||||
showHeatedBed={false}
|
showBed={false}
|
||||||
showMoreInfo={false}
|
showMoreInfo={false}
|
||||||
printerId={loadFilamentStockFormValues.printer._id}
|
id={loadFilamentStockFormValues.printer._id}
|
||||||
shouldUnsubscribe={false}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -250,7 +236,7 @@ const LoadFilamentStock = ({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FilamentStockSelect />
|
<ObjectSelect type='filamentStock' />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -267,26 +253,8 @@ const LoadFilamentStock = ({
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={'middle'}>
|
|
||||||
{!isMobile && (
|
|
||||||
<div style={{ minWidth: '160px' }}>
|
|
||||||
<Steps
|
|
||||||
current={currentStep}
|
|
||||||
items={steps}
|
|
||||||
direction='vertical'
|
|
||||||
style={{ width: 'fit-content' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isMobile && <Divider type={'vertical'} style={{ height: 'unset' }} />}
|
|
||||||
|
|
||||||
<Flex vertical={'true'} style={{ flexGrow: 1 }} gap='middle'>
|
|
||||||
<Title level={2} style={{ marginTop: 0, marginBottom: 4 }}>
|
|
||||||
Load Filament Stock
|
|
||||||
</Title>
|
|
||||||
<Form
|
<Form
|
||||||
name='basic'
|
name='loadFilamentStock'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
form={loadFilamentStockForm}
|
form={loadFilamentStockForm}
|
||||||
onFinish={handleLoadFilamentStock}
|
onFinish={handleLoadFilamentStock}
|
||||||
@ -298,44 +266,15 @@ const LoadFilamentStock = ({
|
|||||||
}
|
}
|
||||||
initialValues={initialLoadFilamentStockForm}
|
initialValues={initialLoadFilamentStockForm}
|
||||||
>
|
>
|
||||||
<div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div>
|
<WizardView
|
||||||
|
title='Load Filament Stock'
|
||||||
<Flex justify={'end'}>
|
steps={steps}
|
||||||
<Button
|
onSubmit={() => loadFilamentStockForm.submit()}
|
||||||
style={{
|
formValid={formValid}
|
||||||
margin: '0 8px'
|
|
||||||
}}
|
|
||||||
onClick={() => setCurrentStep(currentStep - 1)}
|
|
||||||
disabled={!(currentStep > 0)}
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</Button>
|
|
||||||
{currentStep < steps.length - 1 && (
|
|
||||||
<Button
|
|
||||||
type='primary'
|
|
||||||
disabled={!nextEnabled}
|
|
||||||
onClick={() => {
|
|
||||||
setCurrentStep(currentStep + 1)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{currentStep === steps.length - 1 && (
|
|
||||||
<Button
|
|
||||||
type='primary'
|
|
||||||
loading={loadFilamentStockLoading}
|
loading={loadFilamentStockLoading}
|
||||||
onClick={() => {
|
submitText='Done'
|
||||||
loadFilamentStockForm.submit()
|
/>
|
||||||
}}
|
|
||||||
>
|
|
||||||
Done
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,34 +1,30 @@
|
|||||||
import { useState, useContext, useEffect } from 'react'
|
import { useState, useContext, useEffect } from 'react'
|
||||||
import { Form, Button, Typography, Flex, Steps, Divider, Alert } from 'antd'
|
import { Form, Flex, Alert } from 'antd'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { PrintServerContext } from '../../context/PrintServerContext'
|
|
||||||
|
|
||||||
import PrinterSelect from '../../common/PrinterSelect'
|
import ObjectSelect from '../../common/ObjectSelect'
|
||||||
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
|
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
|
||||||
|
import WizardView from '../../common/WizardView'
|
||||||
|
import { ApiServerContext } from '../../context/ApiServerContext'
|
||||||
|
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
const { Title } = Typography
|
|
||||||
|
|
||||||
const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
||||||
|
const { connected, subscribeToObjectEvent, sendObjectAction } =
|
||||||
|
useContext(ApiServerContext)
|
||||||
UnloadFilamentStock.propTypes = {
|
UnloadFilamentStock.propTypes = {
|
||||||
onOk: PropTypes.func.isRequired,
|
onOk: PropTypes.func.isRequired,
|
||||||
reset: PropTypes.bool.isRequired,
|
reset: PropTypes.bool.isRequired,
|
||||||
printer: PropTypes.object
|
printer: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
const { printServer } = useContext(PrintServerContext)
|
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
||||||
|
|
||||||
const initialUnloadFilamentStockForm = {
|
const initialUnloadFilamentStockForm = {
|
||||||
printer: printer
|
printer: printer
|
||||||
}
|
}
|
||||||
|
|
||||||
const [unloadFilamentStockLoading, setUnloadFilamentStockLoading] =
|
const [unloadFilamentStockLoading, setUnloadFilamentStockLoading] =
|
||||||
useState(false)
|
useState(false)
|
||||||
const [currentStep, setCurrentStep] = useState(0)
|
const [formValid, setFormValid] = useState(false)
|
||||||
const [nextEnabled, setNextEnabled] = useState(false)
|
|
||||||
const [currentTemperature, setCurrentTemperature] = useState(-1)
|
const [currentTemperature, setCurrentTemperature] = useState(-1)
|
||||||
const [targetTemperature, setTargetTemperature] = useState(0)
|
const [targetTemperature, setTargetTemperature] = useState(0)
|
||||||
const [filamentSensorDetected, setFilamentSensorDetected] = useState(true)
|
const [filamentSensorDetected, setFilamentSensorDetected] = useState(true)
|
||||||
@ -36,46 +32,35 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
|||||||
const [unloadFilamentStockFormValues, setUnloadFilamentStockFormValues] =
|
const [unloadFilamentStockFormValues, setUnloadFilamentStockFormValues] =
|
||||||
useState(initialUnloadFilamentStockForm)
|
useState(initialUnloadFilamentStockForm)
|
||||||
|
|
||||||
// Add websocket temperature monitoring
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (unloadFilamentStockFormValues.printer) {
|
if (printer?._id && connected) {
|
||||||
const params = {
|
const temperatureEventUnsubscribe = subscribeToObjectEvent(
|
||||||
printerId: unloadFilamentStockFormValues.printer._id,
|
printer._id,
|
||||||
objects: {
|
'printer',
|
||||||
extruder: null,
|
'temperature',
|
||||||
'filament_switch_sensor fsensor': null
|
(event) => {
|
||||||
|
if (event.data?.extruder?.current) {
|
||||||
|
setCurrentTemperature(event.data.extruder.current)
|
||||||
|
}
|
||||||
|
if (event.data?.extruder?.target) {
|
||||||
|
setTargetTemperature(event.data.extruder.target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyStatusUpdate = (statusUpdate) => {
|
|
||||||
if (statusUpdate?.extruder?.temperature !== undefined) {
|
|
||||||
setCurrentTemperature(statusUpdate.extruder.temperature)
|
|
||||||
}
|
|
||||||
if (statusUpdate?.extruder?.target !== undefined) {
|
|
||||||
setTargetTemperature(statusUpdate.extruder.target)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
statusUpdate?.['filament_switch_sensor fsensor']
|
|
||||||
?.filament_detected !== undefined
|
|
||||||
) {
|
|
||||||
setFilamentSensorDetected(
|
|
||||||
Boolean(
|
|
||||||
statusUpdate['filament_switch_sensor fsensor'].filament_detected
|
|
||||||
)
|
)
|
||||||
|
const filamentStockEventUnsubscribe = subscribeToObjectEvent(
|
||||||
|
printer._id,
|
||||||
|
'printer',
|
||||||
|
'filamentSensor',
|
||||||
|
(event) => {
|
||||||
|
setFilamentSensorDetected(event.data.detected)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printServer.emit('printer.objects.subscribe', params)
|
|
||||||
printServer.emit('printer.objects.query', params)
|
|
||||||
printServer.on('notify_status_update', notifyStatusUpdate)
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
printServer.off('notify_status_update', notifyStatusUpdate)
|
if (temperatureEventUnsubscribe) temperatureEventUnsubscribe()
|
||||||
printServer.emit('printer.objects.unsubscribe', params)
|
if (filamentStockEventUnsubscribe) filamentStockEventUnsubscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [printServer, unloadFilamentStockFormValues.printer])
|
}, [printer?._id, connected])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reset) {
|
if (reset) {
|
||||||
@ -90,14 +75,14 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Only enable next if we have a printer selected, we're not loading, and we've reached target temperature
|
// Only enable next if we have a printer selected, we're not loading, and we've reached target temperature
|
||||||
setNextEnabled(
|
setFormValid(
|
||||||
Boolean(unloadFilamentStockFormValues.printer) &&
|
Boolean(unloadFilamentStockFormValues.printer) &&
|
||||||
!unloadFilamentStockLoading &&
|
!unloadFilamentStockLoading &&
|
||||||
currentTemperature + 1 > targetTemperature &&
|
currentTemperature + 1 > targetTemperature &&
|
||||||
targetTemperature != 0
|
targetTemperature != 0
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch(() => setNextEnabled(false))
|
.catch(() => setFormValid(false))
|
||||||
}, [
|
}, [
|
||||||
unloadFilamentStockForm,
|
unloadFilamentStockForm,
|
||||||
unloadFilamentStockFormValues,
|
unloadFilamentStockFormValues,
|
||||||
@ -109,10 +94,13 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
|||||||
const handleUnloadFilamentStock = async () => {
|
const handleUnloadFilamentStock = async () => {
|
||||||
setUnloadFilamentStockLoading(true)
|
setUnloadFilamentStockLoading(true)
|
||||||
// Send G-code to retract the filament
|
// Send G-code to retract the filament
|
||||||
await printServer.emit('printer.gcode.script', {
|
await sendObjectAction(
|
||||||
printerId: unloadFilamentStockFormValues.printer._id,
|
unloadFilamentStockFormValues.printer._id,
|
||||||
script: `_CLIENT_LINEAR_MOVE E=-200 F=1000`
|
'printer',
|
||||||
})
|
{
|
||||||
|
type: 'unloadFilamentStock'
|
||||||
|
}
|
||||||
|
)
|
||||||
//setUnloadFilamentStockLoading(false)
|
//setUnloadFilamentStockLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +128,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<PrinterSelect checkable={false} />
|
<ObjectSelect type='printer' checkable={false} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{unloadFilamentStockLoading == false ? (
|
{unloadFilamentStockLoading == false ? (
|
||||||
@ -182,11 +170,11 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{unloadFilamentStockFormValues.printer ? (
|
{unloadFilamentStockFormValues.printer?._id ? (
|
||||||
<PrinterTemperaturePanel
|
<PrinterTemperaturePanel
|
||||||
showHeatedBed={false}
|
showBed={false}
|
||||||
showMoreInfo={false}
|
showMoreInfo={false}
|
||||||
printerId={unloadFilamentStockFormValues.printer._id}
|
id={unloadFilamentStockFormValues.printer._id}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -195,24 +183,6 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={'middle'}>
|
|
||||||
{!isMobile && (
|
|
||||||
<div style={{ minWidth: '160px' }}>
|
|
||||||
<Steps
|
|
||||||
current={currentStep}
|
|
||||||
items={steps}
|
|
||||||
direction='vertical'
|
|
||||||
style={{ width: 'fit-content' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isMobile && <Divider type={'vertical'} style={{ height: 'unset' }} />}
|
|
||||||
|
|
||||||
<Flex vertical={'true'} style={{ flexGrow: 1 }} gap='middle'>
|
|
||||||
<Title level={2} style={{ marginTop: 0, marginBottom: 4 }}>
|
|
||||||
Unload Filament Stock
|
|
||||||
</Title>
|
|
||||||
<Form
|
<Form
|
||||||
name='unloadFilamentStock'
|
name='unloadFilamentStock'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
@ -226,34 +196,15 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
|
|||||||
}
|
}
|
||||||
initialValues={initialUnloadFilamentStockForm}
|
initialValues={initialUnloadFilamentStockForm}
|
||||||
>
|
>
|
||||||
<div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div>
|
<WizardView
|
||||||
|
title='Unload Filament Stock'
|
||||||
<Flex justify={'end'}>
|
steps={steps}
|
||||||
<Button
|
onSubmit={() => unloadFilamentStockForm.submit()}
|
||||||
style={{
|
formValid={formValid}
|
||||||
margin: '0 8px'
|
|
||||||
}}
|
|
||||||
onClick={() => setCurrentStep(currentStep - 1)}
|
|
||||||
disabled={!(currentStep > 0)}
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</Button>
|
|
||||||
{currentStep === steps.length - 1 && (
|
|
||||||
<Button
|
|
||||||
type='primary'
|
|
||||||
loading={unloadFilamentStockLoading}
|
loading={unloadFilamentStockLoading}
|
||||||
disabled={!nextEnabled}
|
submitText={unloadFilamentStockLoading ? 'Unloading...' : 'Unload'}
|
||||||
onClick={() => {
|
/>
|
||||||
unloadFilamentStockForm.submit()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{unloadFilamentStockLoading ? 'Unloading...' : 'Unload'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,26 @@ import ObjectInfo from '../../common/ObjectInfo'
|
|||||||
import NewObjectForm from '../../common/NewObjectForm'
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
import WizardView from '../../common/WizardView'
|
import WizardView from '../../common/WizardView'
|
||||||
import TemplatePreview from '../../common/TemplatePreview'
|
import TemplatePreview from '../../common/TemplatePreview'
|
||||||
|
import { ApiServerContext } from '../../context/ApiServerContext'
|
||||||
|
import { useContext, useRef, useState } from 'react'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
|
const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
|
||||||
|
const { sendObjectAction, downloadTemplatePDF, formatFileName } =
|
||||||
|
useContext(ApiServerContext)
|
||||||
|
const [downloading, setDownloading] = useState(false)
|
||||||
|
|
||||||
|
// Capture initial default values so later prop changes don't re-initialize the form
|
||||||
|
const defaultValuesRef = useRef({
|
||||||
|
objectType: 'documentJob',
|
||||||
|
...defaultValues,
|
||||||
|
saveToFile: false
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewObjectForm
|
<NewObjectForm
|
||||||
type={'documentJob'}
|
type={'documentJob'}
|
||||||
defaultValues={{ objectType: 'documentJob', ...defaultValues }}
|
defaultValues={defaultValuesRef.current}
|
||||||
>
|
>
|
||||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
const steps = [
|
const steps = [
|
||||||
@ -28,6 +42,10 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
const fileName =
|
||||||
|
formatFileName(
|
||||||
|
objectData?.name + ' ' + dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
) || 'document'
|
||||||
return (
|
return (
|
||||||
<WizardView
|
<WizardView
|
||||||
steps={steps}
|
steps={steps}
|
||||||
@ -35,8 +53,10 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
|
|||||||
title={'Print Document'}
|
title={'Print Document'}
|
||||||
formValid={formValid}
|
formValid={formValid}
|
||||||
loading={submitLoading}
|
loading={submitLoading}
|
||||||
|
disabled={downloading}
|
||||||
|
sideBarGrow={true}
|
||||||
sideBar={
|
sideBar={
|
||||||
<div style={{ minWidth: '400px', minHeight: '500px' }}>
|
<div style={{ minHeight: '500px', flexGrow: 1 }}>
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
objectData={objectData?.object}
|
objectData={objectData?.object}
|
||||||
documentTemplate={objectData?.documentTemplate}
|
documentTemplate={objectData?.documentTemplate}
|
||||||
@ -46,10 +66,52 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
onSubmit={() => {
|
onSubmit={async () => {
|
||||||
handleSubmit()
|
const newDocumentJob = await handleSubmit()
|
||||||
|
if (newDocumentJob.sendToFile == true) {
|
||||||
|
sendObjectAction(newDocumentJob._id, 'documentJob', {
|
||||||
|
type: 'print',
|
||||||
|
data: newDocumentJob
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (onOk) {
|
||||||
onOk()
|
onOk()
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
label: 'Download',
|
||||||
|
steps: ['required'],
|
||||||
|
loading: downloading == true,
|
||||||
|
disabled: downloading == true || submitLoading == true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'PDF',
|
||||||
|
key: 'pdf',
|
||||||
|
onClick: () => {
|
||||||
|
setDownloading(true)
|
||||||
|
downloadTemplatePDF(
|
||||||
|
objectData.documentTemplate._id,
|
||||||
|
objectData.documentTemplate.content,
|
||||||
|
objectData.object,
|
||||||
|
fileName,
|
||||||
|
() => {
|
||||||
|
setDownloading(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'PNG',
|
||||||
|
key: 'png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'JPEG',
|
||||||
|
key: 'jpeg'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useRef } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { Button, Flex, Space, Dropdown } from 'antd'
|
import { Button, Flex, Space, Dropdown, message, Modal } from 'antd'
|
||||||
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
import ObjectTable from '../common/ObjectTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
@ -7,10 +8,12 @@ import GridIcon from '../../Icons/GridIcon'
|
|||||||
import ListIcon from '../../Icons/ListIcon'
|
import ListIcon from '../../Icons/ListIcon'
|
||||||
import useViewMode from '../hooks/useViewMode'
|
import useViewMode from '../hooks/useViewMode'
|
||||||
import ColumnViewButton from '../common/ColumnViewButton'
|
import ColumnViewButton from '../common/ColumnViewButton'
|
||||||
|
import NewDocumentPrinter from './DocumentPrinters/NewDocumentPrinter'
|
||||||
|
|
||||||
const DocumentPrinters = () => {
|
const DocumentPrinters = () => {
|
||||||
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
const tableRef = useRef()
|
const tableRef = useRef()
|
||||||
|
const [newDocumentPrinterOpen, setNewDocumentPrinterOpen] = useState(false)
|
||||||
const [viewMode, setViewMode] = useViewMode('documentPrinter')
|
const [viewMode, setViewMode] = useViewMode('documentPrinter')
|
||||||
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
const [columnVisibility, setColumnVisibility] =
|
||||||
@ -18,6 +21,12 @@ const DocumentPrinters = () => {
|
|||||||
|
|
||||||
const actionItems = {
|
const actionItems = {
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New Document Printer',
|
||||||
|
key: 'newDocumentPrinter',
|
||||||
|
icon: <PlusIcon />
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
label: 'Reload List',
|
label: 'Reload List',
|
||||||
key: 'reloadList',
|
key: 'reloadList',
|
||||||
@ -27,6 +36,8 @@ const DocumentPrinters = () => {
|
|||||||
onClick: ({ key }) => {
|
onClick: ({ key }) => {
|
||||||
if (key === 'reloadList') {
|
if (key === 'reloadList') {
|
||||||
tableRef.current?.reload()
|
tableRef.current?.reload()
|
||||||
|
} else if (key === 'newDocumentPrinter') {
|
||||||
|
setNewDocumentPrinterOpen(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,6 +45,7 @@ const DocumentPrinters = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex vertical={'true'} gap='large'>
|
<Flex vertical={'true'} gap='large'>
|
||||||
|
{contextHolder}
|
||||||
<Flex justify={'space-between'}>
|
<Flex justify={'space-between'}>
|
||||||
<Space size='small'>
|
<Space size='small'>
|
||||||
<Dropdown menu={actionItems}>
|
<Dropdown menu={actionItems}>
|
||||||
@ -62,6 +74,22 @@ const DocumentPrinters = () => {
|
|||||||
cards={viewMode === 'cards'}
|
cards={viewMode === 'cards'}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={newDocumentPrinterOpen}
|
||||||
|
onCancel={() => setNewDocumentPrinterOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
width={700}
|
||||||
|
>
|
||||||
|
<NewDocumentPrinter
|
||||||
|
onOk={() => {
|
||||||
|
setNewDocumentPrinterOpen(false)
|
||||||
|
messageApi.success('New note type created successfully.')
|
||||||
|
tableRef.current?.reload()
|
||||||
|
}}
|
||||||
|
reset={!newDocumentPrinterOpen}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 DocumentPrinterInfo = () => {
|
const DocumentPrinterInfo = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const objectFormRef = useRef(null)
|
||||||
|
const actionHandlerRef = useRef(null)
|
||||||
const documentPrinterId = new URLSearchParams(location.search).get(
|
const documentPrinterId = new URLSearchParams(location.search).get(
|
||||||
'documentPrinterId'
|
'documentPrinterId'
|
||||||
)
|
)
|
||||||
@ -32,52 +35,42 @@ const DocumentPrinterInfo = () => {
|
|||||||
'DocumentPrinterInfo',
|
'DocumentPrinterInfo',
|
||||||
{
|
{
|
||||||
info: true,
|
info: true,
|
||||||
|
stocks: true,
|
||||||
notes: true,
|
notes: true,
|
||||||
auditLogs: true
|
auditLogs: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
const [objectFormState, setEditFormState] = useState({
|
||||||
<ObjectForm
|
isEditing: false,
|
||||||
id={documentPrinterId}
|
editLoading: false,
|
||||||
type='documentPrinter'
|
formValid: false,
|
||||||
style={{ height: '100%' }}
|
locked: false,
|
||||||
>
|
loading: false,
|
||||||
{({
|
objectData: {}
|
||||||
loading,
|
})
|
||||||
isEditing,
|
|
||||||
startEditing,
|
|
||||||
cancelEditing,
|
|
||||||
handleUpdate,
|
|
||||||
formValid,
|
|
||||||
objectData,
|
|
||||||
editLoading,
|
|
||||||
lock,
|
|
||||||
fetchObject
|
|
||||||
}) => {
|
|
||||||
// Define actions for ActionHandler
|
|
||||||
const actions = {
|
const actions = {
|
||||||
reload: () => {
|
reload: () => {
|
||||||
fetchObject()
|
objectFormRef?.current.handleFetchObject()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
edit: () => {
|
edit: () => {
|
||||||
startEditing()
|
objectFormRef?.current.startEditing()
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
cancelEdit: () => {
|
cancelEdit: () => {
|
||||||
cancelEditing()
|
objectFormRef?.current.cancelEditing()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
finishEdit: () => {
|
finishEdit: () => {
|
||||||
handleUpdate()
|
objectFormRef?.current.handleUpdate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionHandler actions={actions} loading={loading}>
|
<>
|
||||||
{({ callAction }) => (
|
|
||||||
<Flex
|
<Flex
|
||||||
gap='large'
|
gap='large'
|
||||||
vertical='true'
|
vertical='true'
|
||||||
@ -92,17 +85,16 @@ const DocumentPrinterInfo = () => {
|
|||||||
<ObjectActions
|
<ObjectActions
|
||||||
type='documentPrinter'
|
type='documentPrinter'
|
||||||
id={documentPrinterId}
|
id={documentPrinterId}
|
||||||
disabled={loading}
|
disabled={objectFormState.loading}
|
||||||
objectData={objectData}
|
objectData={objectFormState.objectData}
|
||||||
/>
|
/>
|
||||||
<ViewButton
|
<ViewButton
|
||||||
disabled={loading}
|
disabled={objectFormState.loading}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: 'info',
|
key: 'info',
|
||||||
label: 'DocumentPrinter Information'
|
label: 'DocumentPrinter Information'
|
||||||
},
|
},
|
||||||
{ key: 'stocks', label: 'DocumentPrinter Stocks' },
|
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
]}
|
]}
|
||||||
@ -111,43 +103,57 @@ const DocumentPrinterInfo = () => {
|
|||||||
/>
|
/>
|
||||||
<DocumentPrintButton
|
<DocumentPrintButton
|
||||||
type='documentPrinter'
|
type='documentPrinter'
|
||||||
objectData={objectData}
|
objectData={objectFormState.objectData}
|
||||||
disabled={loading}
|
disabled={objectFormState.loading}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
<LockIndicator lock={lock} />
|
<LockIndicator lock={objectFormState.lock} />
|
||||||
</Space>
|
</Space>
|
||||||
<Space>
|
<Space>
|
||||||
<EditButtons
|
<EditButtons
|
||||||
isEditing={isEditing}
|
isEditing={objectFormState.isEditing}
|
||||||
handleUpdate={() => {
|
handleUpdate={() => {
|
||||||
callAction('finishEdit')
|
actionHandlerRef.current.callAction('finishEdit')
|
||||||
}}
|
}}
|
||||||
cancelEditing={() => {
|
cancelEditing={() => {
|
||||||
callAction('cancelEdit')
|
actionHandlerRef.current.callAction('cancelEdit')
|
||||||
}}
|
}}
|
||||||
startEditing={() => {
|
startEditing={() => {
|
||||||
callAction('edit')
|
actionHandlerRef.current.callAction('edit')
|
||||||
}}
|
}}
|
||||||
editLoading={editLoading}
|
editLoading={objectFormState.editLoading}
|
||||||
formValid={formValid}
|
formValid={objectFormState.formValid}
|
||||||
disabled={lock?.locked || loading}
|
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||||
loading={editLoading}
|
loading={objectFormState.editLoading}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<div style={{ height: '100%', overflowY: 'scroll' }}>
|
<div style={{ height: '100%', overflowY: 'scroll' }}>
|
||||||
<Flex vertical gap={'large'}>
|
<Flex vertical gap={'large'}>
|
||||||
|
<ActionHandler
|
||||||
|
actions={actions}
|
||||||
|
loading={objectFormState.loading}
|
||||||
|
ref={actionHandlerRef}
|
||||||
|
>
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
title='Document Printer Information'
|
title='Document Printer Information'
|
||||||
icon={<InfoCircleIcon />}
|
icon={<InfoCircleIcon />}
|
||||||
active={collapseState.info}
|
active={collapseState.info}
|
||||||
onToggle={(expanded) =>
|
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||||
updateCollapseState('info', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='info'
|
collapseKey='info'
|
||||||
>
|
>
|
||||||
|
<ObjectForm
|
||||||
|
id={documentPrinterId}
|
||||||
|
type='documentPrinter'
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
ref={objectFormRef}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ loading, isEditing, objectData }) => {
|
||||||
|
return (
|
||||||
<ObjectInfo
|
<ObjectInfo
|
||||||
loading={loading}
|
loading={loading}
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
@ -155,22 +161,21 @@ const DocumentPrinterInfo = () => {
|
|||||||
type='documentPrinter'
|
type='documentPrinter'
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</ObjectForm>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
</ActionHandler>
|
||||||
|
|
||||||
<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>
|
<Card>
|
||||||
<NotesPanel
|
<NotesPanel _id={documentPrinterId} type='documentPrinter' />
|
||||||
_id={documentPrinterId}
|
|
||||||
type='documentPrinter'
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
@ -183,7 +188,7 @@ const DocumentPrinterInfo = () => {
|
|||||||
}
|
}
|
||||||
collapseKey='auditLogs'
|
collapseKey='auditLogs'
|
||||||
>
|
>
|
||||||
{loading ? (
|
{objectFormState.loading ? (
|
||||||
<InfoCollapsePlaceholder />
|
<InfoCollapsePlaceholder />
|
||||||
) : (
|
) : (
|
||||||
<ObjectTable
|
<ObjectTable
|
||||||
@ -196,11 +201,7 @@ const DocumentPrinterInfo = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
</>
|
||||||
</ActionHandler>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</ObjectForm>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
|
const NewDocumentPrinter = ({ onOk }) => {
|
||||||
|
return (
|
||||||
|
<NewObjectForm
|
||||||
|
type={'documentPrinter'}
|
||||||
|
defaultValues={{ active: true, global: false }}
|
||||||
|
>
|
||||||
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='documentPrinter'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Optional',
|
||||||
|
key: 'optional',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='documentPrinter'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={false}
|
||||||
|
visibleProperties={{ content: false, testObject: false }}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Summary',
|
||||||
|
key: 'summary',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='documentPrinter'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
visibleProperties={{
|
||||||
|
_id: false,
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false
|
||||||
|
}}
|
||||||
|
isEditing={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WizardView
|
||||||
|
steps={steps}
|
||||||
|
loading={submitLoading}
|
||||||
|
formValid={formValid}
|
||||||
|
title='New Document Printer'
|
||||||
|
onSubmit={() => {
|
||||||
|
handleSubmit()
|
||||||
|
onOk()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewDocumentPrinter.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
reset: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewDocumentPrinter
|
||||||
@ -1,19 +1,9 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useState } from 'react'
|
|
||||||
import { useMediaQuery } from 'react-responsive'
|
|
||||||
import { Typography, Flex, Steps, Divider } from 'antd'
|
|
||||||
|
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import NewObjectForm from '../../common/NewObjectForm'
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
import NewObjectButtons from '../../common/NewObjectButtons'
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
const { Title } = Typography
|
|
||||||
|
|
||||||
const NewDocumentTemplate = ({ onOk }) => {
|
const NewDocumentTemplate = ({ onOk }) => {
|
||||||
const [currentStep, setCurrentStep] = useState(0)
|
|
||||||
|
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewObjectForm
|
<NewObjectForm
|
||||||
type={'documentTemplate'}
|
type={'documentTemplate'}
|
||||||
@ -69,44 +59,18 @@ const NewDocumentTemplate = ({ onOk }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap='middle'>
|
<WizardView
|
||||||
{!isMobile && (
|
steps={steps}
|
||||||
<div style={{ minWidth: '160px' }}>
|
loading={submitLoading}
|
||||||
<Steps
|
formValid={formValid}
|
||||||
current={currentStep}
|
title='New Document Template'
|
||||||
items={steps}
|
|
||||||
direction='vertical'
|
|
||||||
style={{ width: 'fit-content' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isMobile && (
|
|
||||||
<Divider type='vertical' style={{ height: 'unset' }} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Flex vertical gap='middle' style={{ flexGrow: 1 }}>
|
|
||||||
<Title level={2} style={{ margin: 0 }}>
|
|
||||||
New Document Template
|
|
||||||
</Title>
|
|
||||||
<div style={{ minHeight: '260px', marginBottom: 8 }}>
|
|
||||||
{steps[currentStep].content}
|
|
||||||
</div>
|
|
||||||
<NewObjectButtons
|
|
||||||
currentStep={currentStep}
|
|
||||||
totalSteps={steps.length}
|
|
||||||
onPrevious={() => setCurrentStep((prev) => prev - 1)}
|
|
||||||
onNext={() => setCurrentStep((prev) => prev + 1)}
|
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
handleSubmit()
|
handleSubmit()
|
||||||
onOk()
|
onOk()
|
||||||
}}
|
}}
|
||||||
formValid={formValid}
|
|
||||||
submitLoading={submitLoading}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</NewObjectForm>
|
</NewObjectForm>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useRef, useEffect, useContext } from 'react'
|
import { useState, useRef, useEffect, useContext } from 'react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { Space, Flex, Card, Splitter, Divider } from 'antd'
|
import { Space, Flex, Card, Splitter, Divider, Modal } from 'antd'
|
||||||
import loglevel from 'loglevel'
|
import loglevel from 'loglevel'
|
||||||
import config from '../../../../config.js'
|
import config from '../../../../config.js'
|
||||||
import useCollapseState from '../../hooks/useCollapseState.js'
|
import useCollapseState from '../../hooks/useCollapseState.js'
|
||||||
@ -28,6 +28,9 @@ import { useMediaQuery } from 'react-responsive'
|
|||||||
import AlertsDisplay from '../../common/AlertsDisplay.jsx'
|
import AlertsDisplay from '../../common/AlertsDisplay.jsx'
|
||||||
import { ApiServerContext } from '../../context/ApiServerContext.jsx'
|
import { ApiServerContext } from '../../context/ApiServerContext.jsx'
|
||||||
|
|
||||||
|
import LoadFilamentStock from '../../Inventory/FilamentStocks/LoadFilamentStock.jsx'
|
||||||
|
import UnloadFilamentStock from '../../Inventory/FilamentStocks/UnloadFilamentStock.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('ControlPrinter')
|
const log = loglevel.getLogger('ControlPrinter')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
|
|
||||||
@ -57,6 +60,9 @@ const ControlPrinter = () => {
|
|||||||
collapseState.movement
|
collapseState.movement
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [loadFilamentStockOpen, setLoadFilamentStockOpen] = useState(false)
|
||||||
|
const [unloadFilamentStockOpen, setUnloadFilamentStockOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSideBarVisible(
|
setSideBarVisible(
|
||||||
collapseState.temperature ||
|
collapseState.temperature ||
|
||||||
@ -122,6 +128,39 @@ const ControlPrinter = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
},
|
||||||
|
pauseJob: () => {
|
||||||
|
if (connected == true) {
|
||||||
|
sendObjectAction(printerId, 'printer', {
|
||||||
|
type: 'pauseJob'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
resumeJob: () => {
|
||||||
|
if (connected == true) {
|
||||||
|
sendObjectAction(printerId, 'printer', {
|
||||||
|
type: 'resumeJob'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
cancelJob: () => {
|
||||||
|
if (connected == true) {
|
||||||
|
sendObjectAction(printerId, 'printer', {
|
||||||
|
type: 'cancelJob'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
loadFilamentStock: () => {
|
||||||
|
setLoadFilamentStockOpen(true)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
unloadFilamentStock: () => {
|
||||||
|
setUnloadFilamentStockOpen(true)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +185,7 @@ const ControlPrinter = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Flex
|
<Flex
|
||||||
gap='large'
|
gap='large'
|
||||||
vertical='true'
|
vertical='true'
|
||||||
@ -272,7 +312,15 @@ const ControlPrinter = () => {
|
|||||||
'moonraker.host': false,
|
'moonraker.host': false,
|
||||||
tags: false,
|
tags: false,
|
||||||
firmware: false,
|
firmware: false,
|
||||||
alerts: false
|
alerts: false,
|
||||||
|
online: false,
|
||||||
|
active: false,
|
||||||
|
currentFilamentStock: false,
|
||||||
|
'currentFilamentStock._id': false,
|
||||||
|
currentJob: false,
|
||||||
|
'currentJob._id': false,
|
||||||
|
currentSubJob: false,
|
||||||
|
'currentSubJob._id': false
|
||||||
}}
|
}}
|
||||||
objectData={printerObjectData}
|
objectData={printerObjectData}
|
||||||
type='printer'
|
type='printer'
|
||||||
@ -333,7 +381,7 @@ const ControlPrinter = () => {
|
|||||||
{objectFormState.objectData?.currentSubJob?._id ? (
|
{objectFormState.objectData?.currentSubJob?._id ? (
|
||||||
<ObjectForm
|
<ObjectForm
|
||||||
id={objectFormState.objectData.currentSubJob._id}
|
id={objectFormState.objectData.currentSubJob._id}
|
||||||
type='subjob'
|
type='subJob'
|
||||||
onStateChange={() => {}}
|
onStateChange={() => {}}
|
||||||
>
|
>
|
||||||
{({
|
{({
|
||||||
@ -370,10 +418,12 @@ const ControlPrinter = () => {
|
|||||||
updateCollapseState('filamentStock', expanded)
|
updateCollapseState('filamentStock', expanded)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{objectFormState.objectData?.currentFilamentStock?._id ? (
|
{objectFormState.objectData?.currentFilamentStock
|
||||||
|
?._id ? (
|
||||||
<ObjectForm
|
<ObjectForm
|
||||||
id={
|
id={
|
||||||
objectFormState.objectData.currentFilamentStock._id
|
objectFormState.objectData.currentFilamentStock
|
||||||
|
._id
|
||||||
}
|
}
|
||||||
type='filamentStock'
|
type='filamentStock'
|
||||||
onStateChange={() => {}}
|
onStateChange={() => {}}
|
||||||
@ -438,6 +488,35 @@ const ControlPrinter = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={loadFilamentStockOpen}
|
||||||
|
onCancel={() => setLoadFilamentStockOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
width='700px'
|
||||||
|
destroyOnHidden={true}
|
||||||
|
>
|
||||||
|
<LoadFilamentStock
|
||||||
|
printer={objectFormState.objectData}
|
||||||
|
onOk={() => setLoadFilamentStockOpen(false)}
|
||||||
|
reset={false}
|
||||||
|
filamentStockLoaded={false}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
open={unloadFilamentStockOpen}
|
||||||
|
onCancel={() => setUnloadFilamentStockOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
width='700px'
|
||||||
|
destroyOnHidden={true}
|
||||||
|
>
|
||||||
|
<UnloadFilamentStock
|
||||||
|
printer={objectFormState.objectData}
|
||||||
|
onOk={() => setUnloadFilamentStockOpen(false)}
|
||||||
|
reset={false}
|
||||||
|
filamentStockLoaded={false}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -108,7 +108,14 @@ const DocumentPrintButton = ({
|
|||||||
onCancel={() => setNewDocumentJobOpen(false)}
|
onCancel={() => setNewDocumentJobOpen(false)}
|
||||||
footer={null}
|
footer={null}
|
||||||
destroyOnHidden={true}
|
destroyOnHidden={true}
|
||||||
width={900}
|
width={{
|
||||||
|
xs: '100%',
|
||||||
|
sm: '100%',
|
||||||
|
md: '100%',
|
||||||
|
lg: '90%',
|
||||||
|
xl: '80%',
|
||||||
|
xxl: '80%'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<NewDocumentJob
|
<NewDocumentJob
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
|
|||||||
@ -9,7 +9,8 @@ const NewObjectButtons = ({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
formValid,
|
formValid,
|
||||||
submitLoading,
|
submitLoading,
|
||||||
submitText = 'Done'
|
submitText = 'Done',
|
||||||
|
disabled = false
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Flex justify='end'>
|
<Flex justify='end'>
|
||||||
@ -24,14 +25,18 @@ const NewObjectButtons = ({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{currentStep < totalSteps - 1 ? (
|
{currentStep < totalSteps - 1 ? (
|
||||||
<Button type='primary' disabled={!formValid} onClick={onNext}>
|
<Button
|
||||||
|
type='primary'
|
||||||
|
disabled={!formValid || disabled}
|
||||||
|
onClick={onNext}
|
||||||
|
>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type='primary'
|
type='primary'
|
||||||
loading={submitLoading}
|
loading={submitLoading}
|
||||||
disabled={!formValid}
|
disabled={!formValid || disabled}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
{submitText}
|
{submitText}
|
||||||
@ -49,7 +54,8 @@ NewObjectButtons.propTypes = {
|
|||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
formValid: PropTypes.bool.isRequired,
|
formValid: PropTypes.bool.isRequired,
|
||||||
submitLoading: PropTypes.bool,
|
submitLoading: PropTypes.bool,
|
||||||
submitText: PropTypes.string
|
submitText: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NewObjectButtons
|
export default NewObjectButtons
|
||||||
|
|||||||
@ -138,8 +138,9 @@ const ObjectForm = forwardRef(
|
|||||||
return computedValues
|
return computedValues
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Validate form on change
|
// Validate form on change (debounced to avoid heavy work on every keystroke)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
form
|
form
|
||||||
.validateFields({ validateOnly: true })
|
.validateFields({ validateOnly: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -150,12 +151,16 @@ const ObjectForm = forwardRef(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
setFormValid(false)
|
||||||
onStateChange({
|
onStateChange({
|
||||||
formValid: true,
|
formValid: false,
|
||||||
objectData: { ...serverObjectData, ...form.getFieldsValue() }
|
objectData: { ...serverObjectData, ...form.getFieldsValue() }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, [form, formUpdateValues])
|
}, 150)
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId)
|
||||||
|
}, [form, formUpdateValues, onStateChange, serverObjectData])
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -252,9 +257,14 @@ const ObjectForm = forwardRef(
|
|||||||
updateLockEventHandler
|
updateLockEventHandler
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Debounce objectData updates sent to parent to limit re-renders
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
onStateChange({ objectData })
|
onStateChange({ objectData })
|
||||||
}, [objectData])
|
}, 150)
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId)
|
||||||
|
}, [objectData, onStateChange])
|
||||||
|
|
||||||
const startEditing = () => {
|
const startEditing = () => {
|
||||||
setIsEditing(true)
|
setIsEditing(true)
|
||||||
@ -366,10 +376,19 @@ const ObjectForm = forwardRef(
|
|||||||
model
|
model
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update form with computed values if any were calculated
|
// Update form with computed values if any were calculated and they changed
|
||||||
if (Object.keys(computedValues).length > 0) {
|
if (Object.keys(computedValues).length > 0) {
|
||||||
|
const currentComputedValues = form.getFieldsValue(
|
||||||
|
Object.keys(computedValues)
|
||||||
|
)
|
||||||
|
const hasDiff = Object.keys(computedValues).some(
|
||||||
|
(key) => currentComputedValues[key] !== computedValues[key]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasDiff) {
|
||||||
form.setFieldsValue(computedValues)
|
form.setFieldsValue(computedValues)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Merge all values (user input + computed values)
|
// Merge all values (user input + computed values)
|
||||||
const allValues = { ...values, ...computedValues }
|
const allValues = { ...values, ...computedValues }
|
||||||
|
|||||||
@ -81,6 +81,8 @@ const ObjectProperty = ({
|
|||||||
minimal = false,
|
minimal = false,
|
||||||
previewOpen = false,
|
previewOpen = false,
|
||||||
showPreview = true,
|
showPreview = true,
|
||||||
|
options = [],
|
||||||
|
roundNumber = false,
|
||||||
showHyperlink,
|
showHyperlink,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
@ -166,6 +168,18 @@ const ObjectProperty = ({
|
|||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case 'select': {
|
||||||
|
const selectValue = options.find((option) => option.value === value)
|
||||||
|
if (selectValue) {
|
||||||
|
return <Text {...textParams}>{selectValue.label}</Text>
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text type='secondary' {...textParams}>
|
||||||
|
n/a
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
case 'priceMode':
|
case 'priceMode':
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'margin':
|
case 'margin':
|
||||||
@ -234,10 +248,15 @@ const ObjectProperty = ({
|
|||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
var roundedValue = value
|
||||||
|
if (roundNumber != false) {
|
||||||
|
roundedValue = value.toFixed(roundNumber)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text {...textParams}>
|
<Text {...textParams}>
|
||||||
{prefix}
|
{prefix}
|
||||||
{typeof value === 'number' ? value.toFixed(2) : value}
|
{typeof value === 'number' ? roundedValue : value}
|
||||||
{suffix}
|
{suffix}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
@ -553,6 +572,17 @@ const ObjectProperty = ({
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
|
case 'select':
|
||||||
|
return (
|
||||||
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
|
<Select
|
||||||
|
defaultValue={value}
|
||||||
|
placeholder={'Select a ' + label.toLowerCase() + '...'}
|
||||||
|
disabled={disabled}
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
case 'priceMode':
|
case 'priceMode':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
@ -773,7 +803,8 @@ ObjectProperty.propTypes = {
|
|||||||
height: PropTypes.string,
|
height: PropTypes.string,
|
||||||
previewOpen: PropTypes.bool,
|
previewOpen: PropTypes.bool,
|
||||||
showPreview: PropTypes.bool,
|
showPreview: PropTypes.bool,
|
||||||
showHyperlink: PropTypes.bool
|
showHyperlink: PropTypes.bool,
|
||||||
|
options: PropTypes.array
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectProperty
|
export default ObjectProperty
|
||||||
|
|||||||
@ -144,6 +144,7 @@ const ObjectSelect = ({
|
|||||||
parentKeys: parentKeys.concat(key || '-'),
|
parentKeys: parentKeys.concat(key || '-'),
|
||||||
filterPath: newFilterPath,
|
filterPath: newFilterPath,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
|
|
||||||
children: buildTreeData(
|
children: buildTreeData(
|
||||||
value,
|
value,
|
||||||
pIdx + 1,
|
pIdx + 1,
|
||||||
@ -278,8 +279,11 @@ const ObjectSelect = ({
|
|||||||
handleFetchObjectsProperties()
|
handleFetchObjectsProperties()
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
}
|
}
|
||||||
|
if (value == null) {
|
||||||
|
setTreeSelectValue(null)
|
||||||
|
setInitialized(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleValue()
|
handleValue()
|
||||||
}, [
|
}, [
|
||||||
value,
|
value,
|
||||||
@ -303,8 +307,13 @@ const ObjectSelect = ({
|
|||||||
|
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
setObjectPropertiesTree({})
|
setObjectPropertiesTree({})
|
||||||
|
setObjectList([])
|
||||||
setTreeData([])
|
setTreeData([])
|
||||||
setInitialized(false)
|
setInitialized(false)
|
||||||
|
onTreeSelectChange(null)
|
||||||
|
setTreeSelectValue(null)
|
||||||
|
setInitialLoading(true)
|
||||||
|
setError(false)
|
||||||
prevValuesRef.current = { type, masterFilter }
|
prevValuesRef.current = { type, masterFilter }
|
||||||
}
|
}
|
||||||
}, [type, masterFilter])
|
}, [type, masterFilter])
|
||||||
|
|||||||
@ -227,7 +227,7 @@ const ObjectTable = forwardRef(
|
|||||||
const loadNextPage = useCallback(() => {
|
const loadNextPage = useCallback(() => {
|
||||||
const highestPage = Math.max(...pages.map((p) => p.pageNum))
|
const highestPage = Math.max(...pages.map((p) => p.pageNum))
|
||||||
const nextPage = highestPage + 1
|
const nextPage = highestPage + 1
|
||||||
if (hasMore) {
|
if (hasMore && lazyLoading == false) {
|
||||||
setPages((prev) => {
|
setPages((prev) => {
|
||||||
const filteredPages = prev.map((page) => ({
|
const filteredPages = prev.map((page) => ({
|
||||||
...page,
|
...page,
|
||||||
@ -244,13 +244,13 @@ const ObjectTable = forwardRef(
|
|||||||
})
|
})
|
||||||
fetchData(nextPage)
|
fetchData(nextPage)
|
||||||
}
|
}
|
||||||
}, [pages, createSkeletonData, fetchData, hasMore])
|
}, [pages, createSkeletonData, fetchData, hasMore, lazyLoading])
|
||||||
|
|
||||||
const loadPreviousPage = useCallback(() => {
|
const loadPreviousPage = useCallback(() => {
|
||||||
const lowestPage = Math.min(...pages.map((p) => p.pageNum))
|
const lowestPage = Math.min(...pages.map((p) => p.pageNum))
|
||||||
const prevPage = lowestPage - 1
|
const prevPage = lowestPage - 1
|
||||||
|
|
||||||
if (prevPage > 0) {
|
if (prevPage > 0 && lazyLoading == false) {
|
||||||
setPages((prev) => {
|
setPages((prev) => {
|
||||||
const filteredPages = prev.map((page) => ({
|
const filteredPages = prev.map((page) => ({
|
||||||
...page,
|
...page,
|
||||||
@ -267,7 +267,7 @@ const ObjectTable = forwardRef(
|
|||||||
})
|
})
|
||||||
fetchData(prevPage)
|
fetchData(prevPage)
|
||||||
}
|
}
|
||||||
}, [pages, createSkeletonData, fetchData])
|
}, [pages, createSkeletonData, fetchData, lazyLoading])
|
||||||
|
|
||||||
const handleScroll = useCallback(
|
const handleScroll = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
@ -600,7 +600,7 @@ const ObjectTable = forwardRef(
|
|||||||
title: prop.label,
|
title: prop.label,
|
||||||
dataIndex: prop.name,
|
dataIndex: prop.name,
|
||||||
width: prop.columnWidth || width,
|
width: prop.columnWidth || width,
|
||||||
fixed: fixed,
|
fixed: isMobile ? undefined : fixed,
|
||||||
key: prop.name,
|
key: prop.name,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
if (record?.isSkeleton) {
|
if (record?.isSkeleton) {
|
||||||
@ -651,7 +651,7 @@ const ObjectTable = forwardRef(
|
|||||||
),
|
),
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 80 + rowActions.length * 40, // Adjust width based on number of actions
|
width: 20 + rowActions.length * 30, // Adjust width based on number of actions
|
||||||
render: (record) => {
|
render: (record) => {
|
||||||
return renderActions(record)
|
return renderActions(record)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,8 @@ const ObjectTypeSelect = ({
|
|||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
.map((model) => ({
|
.map((model) => ({
|
||||||
value: model.name,
|
value: model.name,
|
||||||
label: <ObjectTypeDisplay objectType={model.name} />
|
label: <ObjectTypeDisplay objectType={model.name} />,
|
||||||
|
searchText: model.label?.toLowerCase() || ''
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -31,9 +32,7 @@ const ObjectTypeSelect = ({
|
|||||||
allowClear={allowClear}
|
allowClear={allowClear}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
option.label.props.children[1].props.children
|
option.searchText?.includes(input.toLowerCase()) ?? false
|
||||||
.toLowerCase()
|
|
||||||
.indexOf(input.toLowerCase()) >= 0
|
|
||||||
}
|
}
|
||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -4,6 +4,13 @@ import { Progress, Flex, Space } from 'antd'
|
|||||||
import StateTag from './StateTag'
|
import StateTag from './StateTag'
|
||||||
|
|
||||||
const StateDisplay = ({ state, showProgress = true, showState = true }) => {
|
const StateDisplay = ({ state, showProgress = true, showState = true }) => {
|
||||||
|
const loadingProgressTypes = [
|
||||||
|
'loading',
|
||||||
|
'processing',
|
||||||
|
'queued',
|
||||||
|
'printing',
|
||||||
|
'used'
|
||||||
|
]
|
||||||
const currentState = state || {
|
const currentState = state || {
|
||||||
type: 'unknown',
|
type: 'unknown',
|
||||||
progress: 0
|
progress: 0
|
||||||
@ -16,10 +23,14 @@ const StateDisplay = ({ state, showProgress = true, showState = true }) => {
|
|||||||
<StateTag state={currentState.type} />
|
<StateTag state={currentState.type} />
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
{showProgress && currentState?.progress && currentState?.progress > 0 ? (
|
{showProgress &&
|
||||||
|
loadingProgressTypes.includes(currentState.type) &&
|
||||||
|
currentState?.progress &&
|
||||||
|
currentState?.progress > 0 ? (
|
||||||
<Progress
|
<Progress
|
||||||
percent={Math.round(currentState.progress * 100)}
|
percent={Math.round(currentState.progress * 100)}
|
||||||
status='active'
|
status={currentState.type === 'used' ? '' : 'active'}
|
||||||
|
strokeColor={currentState.type === 'used' ? 'orange' : ''}
|
||||||
style={{ width: '150px', marginBottom: '2px' }}
|
style={{ width: '150px', marginBottom: '2px' }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@ -56,9 +56,9 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
|
|||||||
status = 'success'
|
status = 'success'
|
||||||
text = 'Ready'
|
text = 'Ready'
|
||||||
break
|
break
|
||||||
case 'unconsumed':
|
case 'new':
|
||||||
status = 'success'
|
status = 'success'
|
||||||
text = 'Unconsumed'
|
text = 'New'
|
||||||
break
|
break
|
||||||
case 'error':
|
case 'error':
|
||||||
status = 'error'
|
status = 'error'
|
||||||
@ -80,6 +80,10 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
|
|||||||
status = 'warning'
|
status = 'warning'
|
||||||
text = 'Queued'
|
text = 'Queued'
|
||||||
break
|
break
|
||||||
|
case 'used':
|
||||||
|
status = 'warning'
|
||||||
|
text = 'Used'
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
status = 'default'
|
status = 'default'
|
||||||
text = state || 'Unknown'
|
text = state || 'Unknown'
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useContext, useEffect, useRef, useCallback } from 'react'
|
import { useState, useContext, useEffect, useRef, useCallback } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Flex, Button, Input } from 'antd'
|
import { Flex, Button, Input, Select } from 'antd'
|
||||||
import PlusIcon from '../../Icons/PlusIcon.jsx'
|
import PlusIcon from '../../Icons/PlusIcon.jsx'
|
||||||
import MinusIcon from '../../Icons/MinusIcon.jsx'
|
import MinusIcon from '../../Icons/MinusIcon.jsx'
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx'
|
||||||
@ -14,22 +14,26 @@ const TemplatePreview = ({
|
|||||||
isEditing,
|
isEditing,
|
||||||
onTestObjectOpen,
|
onTestObjectOpen,
|
||||||
onPreviewMessage,
|
onPreviewMessage,
|
||||||
showTestObject = false
|
showTestObject = false,
|
||||||
|
showPreviewSwitch = true
|
||||||
}) => {
|
}) => {
|
||||||
const iframeRef = useRef(null)
|
const iframeRef = useRef(null)
|
||||||
const { fetchTemplatePreview } = useContext(ApiServerContext)
|
const { fetchTemplatePreview, fetchTemplatePDF } =
|
||||||
const [previewContent, setPreviewContent] = useState('')
|
useContext(ApiServerContext)
|
||||||
|
const [previewContentHTML, setPreviewContentHTML] = useState('')
|
||||||
|
const [pdfBlob, setPDFBlob] = useState(null)
|
||||||
const [reloadLoading, setReloadLoading] = useState(false)
|
const [reloadLoading, setReloadLoading] = useState(false)
|
||||||
const [previewScale, setPreviewScale] = useState(1)
|
const [previewScale, setPreviewScale] = useState(1)
|
||||||
|
const [previewType, setPreviewType] = useState('HTML')
|
||||||
|
|
||||||
const updatePreviewContent = (html) => {
|
const updatePreviewContentHTML = (html) => {
|
||||||
if (iframeRef.current) {
|
if (iframeRef.current) {
|
||||||
// Save current scroll position
|
// Save current scroll position
|
||||||
const scrollY = iframeRef.current.contentWindow.scrollY
|
const scrollY = iframeRef.current.contentWindow.scrollY
|
||||||
const scrollX = iframeRef.current.contentWindow.scrollX
|
const scrollX = iframeRef.current.contentWindow.scrollX
|
||||||
|
|
||||||
// Update srcDoc
|
// Update srcDoc
|
||||||
setPreviewContent(html)
|
setPreviewContentHTML(html)
|
||||||
|
|
||||||
// Restore scroll position after iframe loads new content
|
// Restore scroll position after iframe loads new content
|
||||||
const handleLoad = () => {
|
const handleLoad = () => {
|
||||||
@ -40,6 +44,23 @@ const TemplatePreview = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reloadPreviewPDF = (content, testObject = {}) => {
|
||||||
|
setReloadLoading(true)
|
||||||
|
fetchTemplatePDF(documentTemplate._id, content, testObject, (result) => {
|
||||||
|
setReloadLoading(false)
|
||||||
|
if (result?.error) {
|
||||||
|
// Handle error through parent component
|
||||||
|
onPreviewMessage(result.error, true)
|
||||||
|
} else {
|
||||||
|
const pdfBlob = new Blob([result.pdf], { type: 'application/pdf' })
|
||||||
|
const pdfUrl = URL.createObjectURL(pdfBlob)
|
||||||
|
|
||||||
|
setPDFBlob(pdfUrl)
|
||||||
|
onPreviewMessage('No issues found.', false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const reloadPreview = useCallback(
|
const reloadPreview = useCallback(
|
||||||
(content, testObject = {}, scale = 1) => {
|
(content, testObject = {}, scale = 1) => {
|
||||||
setReloadLoading(true)
|
setReloadLoading(true)
|
||||||
@ -54,7 +75,7 @@ const TemplatePreview = ({
|
|||||||
// Handle error through parent component
|
// Handle error through parent component
|
||||||
onPreviewMessage(result.error, true)
|
onPreviewMessage(result.error, true)
|
||||||
} else {
|
} else {
|
||||||
updatePreviewContent(result.html)
|
updatePreviewContentHTML(result.html)
|
||||||
onPreviewMessage('No issues found.', false)
|
onPreviewMessage('No issues found.', false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,9 +87,13 @@ const TemplatePreview = ({
|
|||||||
// Move useEffect to component level and use state to track objectData changes
|
// Move useEffect to component level and use state to track objectData changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (documentTemplate?.content) {
|
if (documentTemplate?.content) {
|
||||||
|
if (previewType == 'HTML') {
|
||||||
reloadPreview(documentTemplate.content, objectData, previewScale)
|
reloadPreview(documentTemplate.content, objectData, previewScale)
|
||||||
|
} else {
|
||||||
|
reloadPreviewPDF(documentTemplate.content, objectData)
|
||||||
}
|
}
|
||||||
}, [objectData, documentTemplate, previewScale, reloadPreview])
|
}
|
||||||
|
}, [objectData, documentTemplate, previewScale, previewType])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex vertical gap={'middle'} style={{ height: '100%' }}>
|
<Flex vertical gap={'middle'} style={{ height: '100%' }}>
|
||||||
@ -98,34 +123,51 @@ const TemplatePreview = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={<PlusIcon />}
|
icon={<PlusIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPreviewScale((prev) => prev + 0.05)
|
setPreviewScale((prev) => prev + 0.05)
|
||||||
}}
|
}}
|
||||||
|
disabled={loading || reloadLoading || previewType == 'PDF'}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon={<MinusIcon />}
|
icon={<MinusIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPreviewScale((prev) => prev - 0.05)
|
setPreviewScale((prev) => prev - 0.05)
|
||||||
}}
|
}}
|
||||||
|
disabled={loading || reloadLoading || previewType == 'PDF'}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
style={{ width: '65px' }}
|
style={{ width: '65px' }}
|
||||||
loading={loading || reloadLoading}
|
disabled={loading || reloadLoading || previewType == 'PDF'}
|
||||||
disabled={loading || reloadLoading}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPreviewScale(1)
|
setPreviewScale(1)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{previewScale.toFixed(2)}x
|
{previewScale.toFixed(2)}x
|
||||||
</Button>
|
</Button>
|
||||||
|
{showPreviewSwitch == true ? (
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{ value: 'HTML', label: 'HTML' },
|
||||||
|
{ value: 'PDF', label: 'PDF' }
|
||||||
|
]}
|
||||||
|
loading={loading || reloadLoading}
|
||||||
|
disabled={loading || reloadLoading}
|
||||||
|
value={previewType}
|
||||||
|
onChange={(value) => {
|
||||||
|
setPreviewType(value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<iframe
|
<iframe
|
||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
srcDoc={previewContent}
|
srcDoc={previewType == 'HTML' ? previewContentHTML : undefined}
|
||||||
|
src={previewType == 'PDF' ? pdfBlob : undefined}
|
||||||
frameBorder='0'
|
frameBorder='0'
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -146,7 +188,8 @@ TemplatePreview.propTypes = {
|
|||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
showTestObject: PropTypes.bool,
|
showTestObject: PropTypes.bool,
|
||||||
onTestObjectOpen: PropTypes.func.isRequired,
|
onTestObjectOpen: PropTypes.func.isRequired,
|
||||||
onPreviewMessage: PropTypes.func.isRequired
|
onPreviewMessage: PropTypes.func.isRequired,
|
||||||
|
showPreviewSwitch: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TemplatePreview
|
export default TemplatePreview
|
||||||
|
|||||||
@ -1,7 +1,15 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
import { Typography, Flex, Steps, Divider, Progress } from 'antd'
|
import {
|
||||||
|
Typography,
|
||||||
|
Flex,
|
||||||
|
Steps,
|
||||||
|
Divider,
|
||||||
|
Progress,
|
||||||
|
Button,
|
||||||
|
Dropdown
|
||||||
|
} from 'antd'
|
||||||
import NewObjectButtons from './NewObjectButtons'
|
import NewObjectButtons from './NewObjectButtons'
|
||||||
|
|
||||||
const { Title } = Typography
|
const { Title } = Typography
|
||||||
@ -15,7 +23,10 @@ const WizardView = ({
|
|||||||
loading,
|
loading,
|
||||||
sideBar = null,
|
sideBar = null,
|
||||||
submitText = 'Done',
|
submitText = 'Done',
|
||||||
progress = 0
|
progress = 0,
|
||||||
|
actions = [],
|
||||||
|
sideBarGrow = false,
|
||||||
|
disabled = false
|
||||||
}) => {
|
}) => {
|
||||||
const [currentStep, setCurrentStep] = useState(0)
|
const [currentStep, setCurrentStep] = useState(0)
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
@ -26,7 +37,7 @@ const WizardView = ({
|
|||||||
sideBar != null ? (
|
sideBar != null ? (
|
||||||
sideBar
|
sideBar
|
||||||
) : (
|
) : (
|
||||||
<div style={{ minWidth: '160px' }}>
|
<div style={{ minWidth: sideBarGrow == true ? '100%' : '160px' }}>
|
||||||
<Steps
|
<Steps
|
||||||
current={currentStep}
|
current={currentStep}
|
||||||
items={steps}
|
items={steps}
|
||||||
@ -45,7 +56,13 @@ const WizardView = ({
|
|||||||
vertical
|
vertical
|
||||||
justify='space-between'
|
justify='space-between'
|
||||||
gap={'middle'}
|
gap={'middle'}
|
||||||
style={{ width: '100%' }}
|
style={
|
||||||
|
sideBarGrow == false
|
||||||
|
? { width: '100%' }
|
||||||
|
: isMobile
|
||||||
|
? { width: '100%' }
|
||||||
|
: { width: '400px' }
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Flex vertical gap='middle' style={{ flexGrow: 1, width: '100%' }}>
|
<Flex vertical gap='middle' style={{ flexGrow: 1, width: '100%' }}>
|
||||||
<Title level={2} style={{ margin: 0 }}>
|
<Title level={2} style={{ margin: 0 }}>
|
||||||
@ -63,8 +80,37 @@ const WizardView = ({
|
|||||||
percent={progress}
|
percent={progress}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{(actions || []).map((action) => {
|
||||||
|
if (action.steps.includes(steps[currentStep].key)) {
|
||||||
|
if (action.children) {
|
||||||
|
return (
|
||||||
|
<Dropdown menu={{ items: action.children }} key={action.key}>
|
||||||
|
<Button
|
||||||
|
onClick={action?.onClick}
|
||||||
|
disabled={action?.disabled || disabled}
|
||||||
|
loading={action?.loading || false}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={action.key}
|
||||||
|
onClick={action?.onClick}
|
||||||
|
disabled={action?.disabled || disabled}
|
||||||
|
loading={action?.loading || false}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
|
||||||
<NewObjectButtons
|
<NewObjectButtons
|
||||||
|
disabled={disabled}
|
||||||
currentStep={currentStep}
|
currentStep={currentStep}
|
||||||
totalSteps={steps.length}
|
totalSteps={steps.length}
|
||||||
onPrevious={() => setCurrentStep((prev) => prev - 1)}
|
onPrevious={() => setCurrentStep((prev) => prev - 1)}
|
||||||
@ -87,9 +133,12 @@ WizardView.propTypes = {
|
|||||||
showSteps: PropTypes.bool,
|
showSteps: PropTypes.bool,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
sideBar: PropTypes.node,
|
sideBar: PropTypes.node,
|
||||||
submitText: PropTypes.string,
|
submitText: PropTypes.string,
|
||||||
progress: PropTypes.number
|
progress: PropTypes.number,
|
||||||
|
actions: PropTypes.array,
|
||||||
|
sideBarGrow: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WizardView
|
export default WizardView
|
||||||
|
|||||||
@ -308,9 +308,14 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
.get(callbacksRefKey)
|
.get(callbacksRefKey)
|
||||||
.filter((cb) => cb !== callback)
|
.filter((cb) => cb !== callback)
|
||||||
if (callbacks.length === 0) {
|
if (callbacks.length === 0) {
|
||||||
|
logger.debug(
|
||||||
|
'No callbacks found for object:',
|
||||||
|
callbacksRefKey,
|
||||||
|
'unsubscribing from object update...'
|
||||||
|
)
|
||||||
subscribedCallbacksRef.current.delete(callbacksRefKey)
|
subscribedCallbacksRef.current.delete(callbacksRefKey)
|
||||||
socketRef.current.emit('unsubscribeObjectUpdate', {
|
socketRef.current.emit('unsubscribeObjectUpdate', {
|
||||||
id: id,
|
_id: id,
|
||||||
objectType: objectType
|
objectType: objectType
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -529,7 +534,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
`Added lock callback for object ${id}, total lock callbacks: ${subscribedLockCallbacksRef.current.get(id).length}`
|
`Added lock callback for object ${id}, total lock callbacks: ${subscribedLockCallbacksRef.current.get(id).length}`
|
||||||
)
|
)
|
||||||
|
|
||||||
socketRef.current.emit('subscribe_lock', { id: id, type: type })
|
socketRef.current.emit('subscribe_lock', { _id: id, objectType: type })
|
||||||
logger.debug('Registered lock event listener for object:', id)
|
logger.debug('Registered lock event listener for object:', id)
|
||||||
|
|
||||||
// Return cleanup function
|
// Return cleanup function
|
||||||
@ -853,6 +858,53 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchTemplatePDF = async (id, content, testObject, callback) => {
|
||||||
|
logger.debug('Fetching pdf template...')
|
||||||
|
if (socketRef.current && socketRef.current.connected) {
|
||||||
|
return socketRef.current.emit(
|
||||||
|
'renderTemplatePDF',
|
||||||
|
{
|
||||||
|
_id: id,
|
||||||
|
content: content,
|
||||||
|
object: testObject
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadTemplatePDF = async (
|
||||||
|
id,
|
||||||
|
content,
|
||||||
|
object,
|
||||||
|
filename,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
logger.debug('Downloading template PDF...')
|
||||||
|
|
||||||
|
fetchTemplatePDF(id, content, object, (result) => {
|
||||||
|
logger.debug('Downloading template PDF result:', result)
|
||||||
|
if (result?.error) {
|
||||||
|
console.error(result.error)
|
||||||
|
if (callback) {
|
||||||
|
callback(result.error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const pdfBlob = new Blob([result.pdf], { type: 'application/pdf' })
|
||||||
|
const pdfUrl = URL.createObjectURL(pdfBlob)
|
||||||
|
const fileLink = document.createElement('a')
|
||||||
|
fileLink.href = pdfUrl
|
||||||
|
fileLink.setAttribute('download', `${filename}.pdf`)
|
||||||
|
document.body.appendChild(fileLink)
|
||||||
|
fileLink.click()
|
||||||
|
fileLink.parentNode.removeChild(fileLink)
|
||||||
|
if (callback) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const fetchHostOTP = async (id, callback) => {
|
const fetchHostOTP = async (id, callback) => {
|
||||||
logger.debug('Fetching host OTP...')
|
logger.debug('Fetching host OTP...')
|
||||||
if (socketRef.current && socketRef.current.connected) {
|
if (socketRef.current && socketRef.current.connected) {
|
||||||
@ -942,6 +994,22 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanitize a string so it is safe to use as a filename on most file systems
|
||||||
|
const formatFileName = (name) => {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove characters that are problematic on most common file systems
|
||||||
|
const cleaned = name.replace(/[^a-zA-Z0-9.\-_\s]/g, '')
|
||||||
|
|
||||||
|
// Normalize whitespace to single underscores
|
||||||
|
const normalized = cleaned.trim().replace(/\s+/g, '_')
|
||||||
|
|
||||||
|
// Most file systems limit filenames to 255 characters
|
||||||
|
return normalized.slice(0, 255)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ApiServerContext.Provider
|
<ApiServerContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -967,11 +1035,14 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
showError,
|
showError,
|
||||||
fetchFileContent,
|
fetchFileContent,
|
||||||
fetchTemplatePreview,
|
fetchTemplatePreview,
|
||||||
|
fetchTemplatePDF,
|
||||||
fetchNotes,
|
fetchNotes,
|
||||||
|
downloadTemplatePDF,
|
||||||
fetchHostOTP,
|
fetchHostOTP,
|
||||||
sendObjectAction,
|
sendObjectAction,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
flushFile
|
flushFile,
|
||||||
|
formatFileName
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
|
|||||||
@ -6,9 +6,9 @@ const config = {
|
|||||||
logLevel: 'trace'
|
logLevel: 'trace'
|
||||||
},
|
},
|
||||||
production: {
|
production: {
|
||||||
backendUrl: 'http://192.168.68.53:8080',
|
backendUrl: 'https://dev.tombutcher.work/api',
|
||||||
printServerUrl: 'ws://192.168.68.53:8081',
|
printServerUrl: 'ws://192.168.68.53:8081',
|
||||||
apiServerUrl: 'ws://192.168.68.53:9090',
|
apiServerUrl: 'https://dev-wss.tombutcher.work',
|
||||||
logLevel: 'error'
|
logLevel: 'error'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,12 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
|||||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||||
import EditIcon from '../../components/Icons/EditIcon'
|
import EditIcon from '../../components/Icons/EditIcon'
|
||||||
import DocumentJobIcon from '../../components/Icons/DocumentJobIcon'
|
import DocumentJobIcon from '../../components/Icons/DocumentJobIcon'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
export const DocumentJob = {
|
export const DocumentJob = {
|
||||||
name: 'documentJob',
|
name: 'documentJob',
|
||||||
label: 'Document Job',
|
label: 'Document Job',
|
||||||
prefix: 'DSZ',
|
prefix: 'DJB',
|
||||||
icon: DocumentJobIcon,
|
icon: DocumentJobIcon,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
@ -60,7 +61,9 @@ export const DocumentJob = {
|
|||||||
columnWidth: 200,
|
columnWidth: 200,
|
||||||
columnFixed: 'left',
|
columnFixed: 'left',
|
||||||
value: (objectData) => {
|
value: (objectData) => {
|
||||||
return `${objectData?.documentTemplate?.name || 'No template'} (${objectData?.object?.name || 'No name'})`
|
if (objectData?.createdAt == undefined) {
|
||||||
|
return `${objectData?.documentTemplate?.name || 'No template'} ${dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')} (${objectData?.object?.name || objectData?.object?._id})`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -69,6 +72,14 @@ export const DocumentJob = {
|
|||||||
type: 'dateTime',
|
type: 'dateTime',
|
||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'state',
|
||||||
|
label: 'Status',
|
||||||
|
type: 'state',
|
||||||
|
objectType: 'printer',
|
||||||
|
showName: false,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'objectType',
|
name: 'objectType',
|
||||||
label: 'Object Type',
|
label: 'Object Type',
|
||||||
@ -86,6 +97,15 @@ export const DocumentJob = {
|
|||||||
return objectData?.objectType
|
return objectData?.objectType
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'object._id',
|
||||||
|
label: 'Object ID',
|
||||||
|
type: 'id',
|
||||||
|
showHyperlink: true,
|
||||||
|
objectType: (objectData) => {
|
||||||
|
return objectData?.objectType
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'documentTemplate',
|
name: 'documentTemplate',
|
||||||
label: 'Template',
|
label: 'Template',
|
||||||
@ -101,6 +121,13 @@ export const DocumentJob = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'documentTemplate._id',
|
||||||
|
label: 'Template ID',
|
||||||
|
type: 'id',
|
||||||
|
showHyperlink: true,
|
||||||
|
objectType: 'documentTemplate'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'documentPrinter',
|
name: 'documentPrinter',
|
||||||
label: 'Printer',
|
label: 'Printer',
|
||||||
@ -114,6 +141,13 @@ export const DocumentJob = {
|
|||||||
online: true
|
online: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'documentPrinter._id',
|
||||||
|
label: 'Printer ID',
|
||||||
|
type: 'id',
|
||||||
|
showHyperlink: true,
|
||||||
|
objectType: 'documentPrinter'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,52 +74,88 @@ export const DocumentPrinter = {
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'documentSize',
|
name: 'state',
|
||||||
label: 'Document Size',
|
label: 'Status',
|
||||||
|
type: 'state',
|
||||||
|
objectType: 'printer',
|
||||||
|
showName: false,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'active',
|
||||||
|
label: 'Active',
|
||||||
|
type: 'bool',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'online',
|
||||||
|
label: 'Online',
|
||||||
|
type: 'bool',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'host',
|
||||||
|
label: 'Host',
|
||||||
required: true,
|
required: true,
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
objectType: 'host',
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'host._id',
|
||||||
|
label: 'Host ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'host',
|
||||||
|
showCopy: true,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'connection.mode',
|
||||||
|
label: 'Mode',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: 'Network', value: 'network' },
|
||||||
|
{ label: 'Serial', value: 'serial' }
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'connection.interface',
|
||||||
|
label: 'Interface',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: 'CUPS', value: 'cups' },
|
||||||
|
{ label: 'Epson Receipt', value: 'epsonReceipt' },
|
||||||
|
{ label: 'Star Receipt', value: 'starReceipt' }
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'connection.host',
|
||||||
|
label: 'Connection String',
|
||||||
|
type: 'text',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'currentDocumentSize',
|
||||||
|
label: 'Current Document Size',
|
||||||
|
required: false,
|
||||||
|
type: 'object',
|
||||||
objectType: 'documentSize'
|
objectType: 'documentSize'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'documentSize._id',
|
name: 'currentDocumentSize._id',
|
||||||
label: 'Document Size ID',
|
label: 'Current Document Size ID',
|
||||||
type: 'id',
|
type: 'id',
|
||||||
objectType: 'documentSize',
|
objectType: 'documentSize',
|
||||||
showCopy: true,
|
showCopy: true,
|
||||||
showHyperlink: true
|
showHyperlink: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'active',
|
|
||||||
label: 'Active',
|
|
||||||
required: true,
|
|
||||||
type: 'bool'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'tags',
|
name: 'tags',
|
||||||
label: 'Tags',
|
label: 'Tags',
|
||||||
required: false,
|
required: false,
|
||||||
type: 'tags'
|
type: 'tags'
|
||||||
},
|
|
||||||
{ name: 'global', label: 'Global', required: false, type: 'bool' },
|
|
||||||
{
|
|
||||||
name: 'parent',
|
|
||||||
label: 'Parent',
|
|
||||||
required: false,
|
|
||||||
type: 'object',
|
|
||||||
objectType: 'documentPrinter',
|
|
||||||
disabled: (documentPrinter) => {
|
|
||||||
if (documentPrinter.global == true) {
|
|
||||||
documentPrinter.parent = null
|
|
||||||
}
|
|
||||||
return documentPrinter.global
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parent._id',
|
|
||||||
label: 'Parent ID',
|
|
||||||
required: false,
|
|
||||||
type: 'id',
|
|
||||||
objectType: 'documentPrinter'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import EditIcon from '../../components/Icons/EditIcon'
|
|||||||
import PlayCircleIcon from '../../components/Icons/PlayCircleIcon'
|
import PlayCircleIcon from '../../components/Icons/PlayCircleIcon'
|
||||||
import PauseCircleIcon from '../../components/Icons/PauseCircleIcon'
|
import PauseCircleIcon from '../../components/Icons/PauseCircleIcon'
|
||||||
import StopCircleIcon from '../../components/Icons/StopCircleIcon'
|
import StopCircleIcon from '../../components/Icons/StopCircleIcon'
|
||||||
|
import FilamentStockIcon from '../../components/Icons/FilamentStockIcon'
|
||||||
export const Printer = {
|
export const Printer = {
|
||||||
name: 'printer',
|
name: 'printer',
|
||||||
label: 'Printer',
|
label: 'Printer',
|
||||||
@ -94,8 +94,8 @@ export const Printer = {
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'Start',
|
name: 'startQueue',
|
||||||
label: 'Start',
|
label: 'Start Queue',
|
||||||
icon: PlayCircleIcon,
|
icon: PlayCircleIcon,
|
||||||
disabled: (objectData) => {
|
disabled: (objectData) => {
|
||||||
console.log(objectData?.subJobs?.length)
|
console.log(objectData?.subJobs?.length)
|
||||||
@ -109,28 +109,60 @@ export const Printer = {
|
|||||||
url: (_id) =>
|
url: (_id) =>
|
||||||
`/dashboard/production/printers/control?printerId=${_id}&action=startQueue`
|
`/dashboard/production/printers/control?printerId=${_id}&action=startQueue`
|
||||||
},
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
name: 'pause',
|
name: 'pauseJob',
|
||||||
label: 'Pause',
|
label: 'Pause Job',
|
||||||
icon: PauseCircleIcon,
|
icon: PauseCircleIcon,
|
||||||
disabled: (objectData) => {
|
disabled: (objectData) => {
|
||||||
return objectData?.state?.type != 'printing'
|
return objectData?.state?.type != 'printing'
|
||||||
},
|
},
|
||||||
url: (_id) =>
|
url: (_id) =>
|
||||||
`/dashboard/production/printers/control?printerId=${_id}&action=pauseQueue`
|
`/dashboard/production/printers/control?printerId=${_id}&action=pauseJob`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Stop',
|
name: 'resumeJob',
|
||||||
label: 'Stop',
|
label: 'Resume Job',
|
||||||
|
icon: PlayCircleIcon,
|
||||||
|
disabled: (objectData) => {
|
||||||
|
return objectData?.state?.type != 'printing'
|
||||||
|
},
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/production/printers/control?printerId=${_id}&action=resumeJob`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelJob',
|
||||||
|
label: 'Cancel Job',
|
||||||
icon: StopCircleIcon,
|
icon: StopCircleIcon,
|
||||||
disabled: (objectData) => {
|
disabled: (objectData) => {
|
||||||
return (
|
return (
|
||||||
objectData?.state?.type != 'printing' ||
|
objectData?.state?.type != 'printing' &&
|
||||||
objectData?.state?.type != 'error'
|
objectData?.state?.type != 'error'
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
url: (_id) =>
|
url: (_id) =>
|
||||||
`/dashboard/production/printers/control?printerId=${_id}&action=stopQueue`
|
`/dashboard/production/printers/control?printerId=${_id}&action=cancelJob`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'filamentStock',
|
||||||
|
label: 'Filament Stock',
|
||||||
|
icon: FilamentStockIcon,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'loadFilamentStock',
|
||||||
|
label: 'Load Filament Stock',
|
||||||
|
icon: FilamentStockIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/production/printers/control?printerId=${_id}&action=loadFilamentStock`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'unloadFilamentStock',
|
||||||
|
label: 'Unload Filament Stock',
|
||||||
|
icon: FilamentStockIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/production/printers/control?printerId=${_id}&action=unloadFilamentStock`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -299,6 +331,14 @@ export const Printer = {
|
|||||||
label: 'Alerts',
|
label: 'Alerts',
|
||||||
type: 'alerts',
|
type: 'alerts',
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'subJobs',
|
||||||
|
label: 'Queue',
|
||||||
|
type: 'objectList',
|
||||||
|
objectType: 'subJob',
|
||||||
|
required: false,
|
||||||
|
readOnly: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user