Compare commits

..

No commits in common. "c6c99bb02cb6c21eb59979153745a94e1e65aafc" and "c2f55a596784d6f210526e9be0709ec8ba1a998d" have entirely different histories.

26 changed files with 983 additions and 2123 deletions

View File

@ -55,9 +55,7 @@
-webkit-app-region: drag; -webkit-app-region: drag;
} }
.electron-navigation-wrapper li, .electron-navigation-wrapper li, .electron-navigation-wrapper button, .electron-navigation-wrapper .ant-tag{
.electron-navigation-wrapper button,
.electron-navigation-wrapper .ant-tag {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
@ -69,8 +67,7 @@
padding-inline: 10px; padding-inline: 10px;
} }
.electron-sidebar .ant-menu-item, .electron-sidebar .ant-menu-item, .electron-sidebar .ant-menu-submenu-title {
.electron-sidebar .ant-menu-submenu-title {
height: 32.5px !important; height: 32.5px !important;
line-height: 32.5px !important; line-height: 32.5px !important;
} }
@ -84,6 +81,7 @@
display: none; display: none;
} }
:root { :root {
--unit-100vh: 100vh; --unit-100vh: 100vh;
} }
@ -231,17 +229,15 @@ body {
table-layout: fixed !important; table-layout: fixed !important;
} }
.objectTableDescritions .objectTableDescritions >.ant-descriptions-view .ant-descriptions-row >.ant-descriptions-item-label {
> .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;
@ -253,6 +249,7 @@ body {
--sb-size: 8px; --sb-size: 8px;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
} }
@ -310,7 +307,3 @@ 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

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,27 @@
import { useState, useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react'
import { Form, Flex, Descriptions, Alert } from 'antd' import {
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 ObjectSelect from '../../common/ObjectSelect' import PrinterState from '../../common/StateDisplay'
import ObjectDisplay from '../../common/ObjectDisplay'
import WizardView from '../../common/WizardView' const { Title } = Typography
import { ApiServerContext } from '../../context/ApiServerContext'
const LoadFilamentStock = ({ const LoadFilamentStock = ({
onOk, onOk,
@ -16,8 +29,7 @@ const LoadFilamentStock = ({
printer = null, printer = null,
filamentStockLoaded = false filamentStockLoaded = false
}) => { }) => {
const { connected, subscribeToObjectEvent, sendObjectAction } = const isMobile = useMediaQuery({ maxWidth: 768 })
useContext(ApiServerContext)
LoadFilamentStock.propTypes = { LoadFilamentStock.propTypes = {
onOk: PropTypes.func.isRequired, onOk: PropTypes.func.isRequired,
@ -26,6 +38,8 @@ const LoadFilamentStock = ({
filamentStockLoaded: PropTypes.bool filamentStockLoaded: PropTypes.bool
} }
const { printServer } = useContext(PrintServerContext)
const initialLoadFilamentStockForm = { const initialLoadFilamentStockForm = {
printer: printer, printer: printer,
filamentStock: null filamentStock: null
@ -33,7 +47,8 @@ const LoadFilamentStock = ({
const [loadFilamentStockLoading, setLoadFilamentStockLoading] = const [loadFilamentStockLoading, setLoadFilamentStockLoading] =
useState(false) useState(false)
const [formValid, setFormValid] = useState(false) const [currentStep, setCurrentStep] = useState(0)
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] =
@ -47,69 +62,77 @@ const LoadFilamentStock = ({
loadFilamentStockForm loadFilamentStockForm
) )
// Add websocket temperature monitoring
useEffect(() => { useEffect(() => {
if (printer?._id && connected) { if (loadFilamentStockFormValues.printer) {
const temperatureEventUnsubscribe = subscribeToObjectEvent( const params = {
printer._id, printerId: loadFilamentStockFormValues.printer._id,
'printer', objects: {
'temperature', extruder: null,
(event) => { 'filament_switch_sensor fsensor': null
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 () => {
if (temperatureEventUnsubscribe) temperatureEventUnsubscribe() printServer.off('notify_status_update', notifyStatusUpdate)
if (filamentStockEventUnsubscribe) filamentStockEventUnsubscribe() printServer.emit('printer.objects.unsubscribe', params)
} }
} }
}, [printer?._id, connected]) }, [printServer, loadFilamentStockFormValues.printer])
useEffect(() => { useEffect(() => {
// Validate form fields
loadFilamentStockForm loadFilamentStockForm
.validateFields({ .validateFields({
validateOnly: true validateOnly: true
}) })
.then(() => { .then(() => setNextEnabled(filamentSensorDetected))
const hasPrinter = Boolean(loadFilamentStockFormValues.printer) .catch(() => setNextEnabled(false))
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,
loadFilamentStockFormValues, filamentSensorDetected
])
useEffect(() => {
if (
filamentSensorDetected == true &&
currentTemperature >= targetTemperature
) {
setNextEnabled(filamentSensorDetected)
if (currentStep == 0) {
setCurrentStep(1)
}
} else if (filamentSensorDetected == false) {
setCurrentStep(0)
}
}, [
filamentSensorDetected, filamentSensorDetected,
targetTemperature,
currentTemperature, currentTemperature,
targetTemperature currentStep
]) ])
const summaryItems = [ const summaryItems = [
@ -117,9 +140,8 @@ const LoadFilamentStock = ({
key: 'filamentStock', key: 'filamentStock',
label: 'Stock', label: 'Stock',
children: loadFilamentStockFormValues.filamentStock ? ( children: loadFilamentStockFormValues.filamentStock ? (
<ObjectDisplay <FilamentStockDisplay
objectType='filamentStock' filamentStock={loadFilamentStockFormValues.filamentStock}
object={loadFilamentStockFormValues.filamentStock}
/> />
) : ( ) : (
'n/a' 'n/a'
@ -129,12 +151,9 @@ const LoadFilamentStock = ({
key: 'printer', key: 'printer',
label: 'Printer', label: 'Printer',
children: loadFilamentStockFormValues.printer ? ( children: loadFilamentStockFormValues.printer ? (
<ObjectDisplay <PrinterState printer={loadFilamentStockFormValues.printer} />
objectType='printer'
object={loadFilamentStockFormValues.printer}
/>
) : ( ) : (
'n/a' 'n/a>'
) )
} }
] ]
@ -150,16 +169,10 @@ const LoadFilamentStock = ({
try { try {
// Set the extruder temperature // Set the extruder temperature
await sendObjectAction( await printServer.emit('printer.filamentstock.load', {
loadFilamentStockFormValues.printer._id, printerId: loadFilamentStockFormValues.printer._id,
'printer', filamentStockId: loadFilamentStockFormValues.filamentStock._id
{ })
type: 'loadFilamentStock',
data: {
filamentStock: loadFilamentStockFormValues.filamentStock
}
}
)
onOk() onOk()
} finally { } finally {
setLoadFilamentStockLoading(false) setLoadFilamentStockLoading(false)
@ -183,7 +196,7 @@ const LoadFilamentStock = ({
} }
]} ]}
> >
<ObjectSelect type='printer' checkable={false} /> <PrinterSelect checkable={false} />
</Form.Item> </Form.Item>
{targetTemperature == 0 ? ( {targetTemperature == 0 ? (
<Alert <Alert
@ -213,9 +226,10 @@ const LoadFilamentStock = ({
{loadFilamentStockFormValues.printer ? ( {loadFilamentStockFormValues.printer ? (
<PrinterTemperaturePanel <PrinterTemperaturePanel
showBed={false} showHeatedBed={false}
showMoreInfo={false} showMoreInfo={false}
id={loadFilamentStockFormValues.printer._id} printerId={loadFilamentStockFormValues.printer._id}
shouldUnsubscribe={false}
/> />
) : null} ) : null}
</Flex> </Flex>
@ -236,7 +250,7 @@ const LoadFilamentStock = ({
} }
]} ]}
> >
<ObjectSelect type='filamentStock' /> <FilamentStockSelect />
</Form.Item> </Form.Item>
</> </>
) )
@ -253,8 +267,26 @@ 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='loadFilamentStock' name='basic'
autoComplete='off' autoComplete='off'
form={loadFilamentStockForm} form={loadFilamentStockForm}
onFinish={handleLoadFilamentStock} onFinish={handleLoadFilamentStock}
@ -266,15 +298,44 @@ const LoadFilamentStock = ({
} }
initialValues={initialLoadFilamentStockForm} initialValues={initialLoadFilamentStockForm}
> >
<WizardView <div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div>
title='Load Filament Stock'
steps={steps} <Flex justify={'end'}>
onSubmit={() => loadFilamentStockForm.submit()} <Button
formValid={formValid} style={{
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}
submitText='Done' onClick={() => {
/> loadFilamentStockForm.submit()
}}
>
Done
</Button>
)}
</Flex>
</Form> </Form>
</Flex>
</Flex>
) )
} }

View File

@ -1,30 +1,34 @@
import { useState, useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react'
import { Form, Flex, Alert } from 'antd' import { Form, Button, Typography, Flex, Steps, Divider, Alert } from 'antd'
import { useMediaQuery } from 'react-responsive'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { PrintServerContext } from '../../context/PrintServerContext'
import ObjectSelect from '../../common/ObjectSelect' import PrinterSelect from '../../common/PrinterSelect'
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 [formValid, setFormValid] = useState(false) const [currentStep, setCurrentStep] = useState(0)
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)
@ -32,35 +36,46 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
const [unloadFilamentStockFormValues, setUnloadFilamentStockFormValues] = const [unloadFilamentStockFormValues, setUnloadFilamentStockFormValues] =
useState(initialUnloadFilamentStockForm) useState(initialUnloadFilamentStockForm)
// Add websocket temperature monitoring
useEffect(() => { useEffect(() => {
if (printer?._id && connected) { if (unloadFilamentStockFormValues.printer) {
const temperatureEventUnsubscribe = subscribeToObjectEvent( const params = {
printer._id, printerId: unloadFilamentStockFormValues.printer._id,
'printer', objects: {
'temperature', extruder: null,
(event) => { 'filament_switch_sensor fsensor': null
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 () => {
if (temperatureEventUnsubscribe) temperatureEventUnsubscribe() printServer.off('notify_status_update', notifyStatusUpdate)
if (filamentStockEventUnsubscribe) filamentStockEventUnsubscribe() printServer.emit('printer.objects.unsubscribe', params)
} }
} }
}, [printer?._id, connected]) }, [printServer, unloadFilamentStockFormValues.printer])
useEffect(() => { useEffect(() => {
if (reset) { if (reset) {
@ -75,14 +90,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
setFormValid( setNextEnabled(
Boolean(unloadFilamentStockFormValues.printer) && Boolean(unloadFilamentStockFormValues.printer) &&
!unloadFilamentStockLoading && !unloadFilamentStockLoading &&
currentTemperature + 1 > targetTemperature && currentTemperature + 1 > targetTemperature &&
targetTemperature != 0 targetTemperature != 0
) )
}) })
.catch(() => setFormValid(false)) .catch(() => setNextEnabled(false))
}, [ }, [
unloadFilamentStockForm, unloadFilamentStockForm,
unloadFilamentStockFormValues, unloadFilamentStockFormValues,
@ -94,13 +109,10 @@ 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 sendObjectAction( await printServer.emit('printer.gcode.script', {
unloadFilamentStockFormValues.printer._id, printerId: unloadFilamentStockFormValues.printer._id,
'printer', script: `_CLIENT_LINEAR_MOVE E=-200 F=1000`
{ })
type: 'unloadFilamentStock'
}
)
//setUnloadFilamentStockLoading(false) //setUnloadFilamentStockLoading(false)
} }
@ -128,7 +140,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
} }
]} ]}
> >
<ObjectSelect type='printer' checkable={false} /> <PrinterSelect checkable={false} />
</Form.Item> </Form.Item>
{unloadFilamentStockLoading == false ? ( {unloadFilamentStockLoading == false ? (
@ -170,11 +182,11 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
/> />
)} )}
{unloadFilamentStockFormValues.printer?._id ? ( {unloadFilamentStockFormValues.printer ? (
<PrinterTemperaturePanel <PrinterTemperaturePanel
showBed={false} showHeatedBed={false}
showMoreInfo={false} showMoreInfo={false}
id={unloadFilamentStockFormValues.printer._id} printerId={unloadFilamentStockFormValues.printer._id}
/> />
) : null} ) : null}
</Flex> </Flex>
@ -183,6 +195,24 @@ 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'
@ -196,15 +226,34 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
} }
initialValues={initialUnloadFilamentStockForm} initialValues={initialUnloadFilamentStockForm}
> >
<WizardView <div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div>
title='Unload Filament Stock'
steps={steps} <Flex justify={'end'}>
onSubmit={() => unloadFilamentStockForm.submit()} <Button
formValid={formValid} style={{
margin: '0 8px'
}}
onClick={() => setCurrentStep(currentStep - 1)}
disabled={!(currentStep > 0)}
>
Previous
</Button>
{currentStep === steps.length - 1 && (
<Button
type='primary'
loading={unloadFilamentStockLoading} loading={unloadFilamentStockLoading}
submitText={unloadFilamentStockLoading ? 'Unloading...' : 'Unload'} disabled={!nextEnabled}
/> onClick={() => {
unloadFilamentStockForm.submit()
}}
>
{unloadFilamentStockLoading ? 'Unloading...' : 'Unload'}
</Button>
)}
</Flex>
</Form> </Form>
</Flex>
</Flex>
) )
} }

View File

@ -3,26 +3,12 @@ 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={defaultValuesRef.current} defaultValues={{ objectType: 'documentJob', ...defaultValues }}
> >
{({ handleSubmit, submitLoading, objectData, formValid }) => { {({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [ const steps = [
@ -42,10 +28,6 @@ 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}
@ -53,10 +35,8 @@ 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={{ minHeight: '500px', flexGrow: 1 }}> <div style={{ minWidth: '400px', minHeight: '500px' }}>
<TemplatePreview <TemplatePreview
objectData={objectData?.object} objectData={objectData?.object}
documentTemplate={objectData?.documentTemplate} documentTemplate={objectData?.documentTemplate}
@ -66,52 +46,10 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
/> />
</div> </div>
} }
onSubmit={async () => { onSubmit={() => {
const newDocumentJob = await handleSubmit() 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'
}
]
}
]}
/> />
) )
}} }}

View File

@ -1,6 +1,5 @@
import { useRef, useState } from 'react' import { useRef } from 'react'
import { Button, Flex, Space, Dropdown, message, Modal } from 'antd' import { Button, Flex, Space, Dropdown } 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'
@ -8,12 +7,10 @@ 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] =
@ -21,12 +18,6 @@ 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',
@ -36,8 +27,6 @@ const DocumentPrinters = () => {
onClick: ({ key }) => { onClick: ({ key }) => {
if (key === 'reloadList') { if (key === 'reloadList') {
tableRef.current?.reload() tableRef.current?.reload()
} else if (key === 'newDocumentPrinter') {
setNewDocumentPrinterOpen(true)
} }
} }
} }
@ -45,7 +34,6 @@ 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}>
@ -74,22 +62,6 @@ 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>
</> </>
) )
} }

View File

@ -1,4 +1,3 @@
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'
@ -26,8 +25,6 @@ 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'
) )
@ -35,42 +32,52 @@ const DocumentPrinterInfo = () => {
'DocumentPrinterInfo', 'DocumentPrinterInfo',
{ {
info: true, info: true,
stocks: true,
notes: true, notes: true,
auditLogs: true auditLogs: true
} }
) )
const [objectFormState, setEditFormState] = useState({ return (
isEditing: false, <ObjectForm
editLoading: false, id={documentPrinterId}
formValid: false, type='documentPrinter'
locked: false, style={{ height: '100%' }}
loading: false, >
objectData: {} {({
}) loading,
isEditing,
startEditing,
cancelEditing,
handleUpdate,
formValid,
objectData,
editLoading,
lock,
fetchObject
}) => {
// Define actions for ActionHandler
const actions = { const actions = {
reload: () => { reload: () => {
objectFormRef?.current.handleFetchObject() fetchObject()
return true return true
}, },
edit: () => { edit: () => {
objectFormRef?.current.startEditing() startEditing()
return false return false
}, },
cancelEdit: () => { cancelEdit: () => {
objectFormRef?.current.cancelEditing() cancelEditing()
return true return true
}, },
finishEdit: () => { finishEdit: () => {
objectFormRef?.current.handleUpdate() handleUpdate()
return true return true
} }
} }
return ( return (
<> <ActionHandler actions={actions} loading={loading}>
{({ callAction }) => (
<Flex <Flex
gap='large' gap='large'
vertical='true' vertical='true'
@ -85,16 +92,17 @@ const DocumentPrinterInfo = () => {
<ObjectActions <ObjectActions
type='documentPrinter' type='documentPrinter'
id={documentPrinterId} id={documentPrinterId}
disabled={objectFormState.loading} disabled={loading}
objectData={objectFormState.objectData} objectData={objectData}
/> />
<ViewButton <ViewButton
disabled={objectFormState.loading} disabled={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' }
]} ]}
@ -103,57 +111,43 @@ const DocumentPrinterInfo = () => {
/> />
<DocumentPrintButton <DocumentPrintButton
type='documentPrinter' type='documentPrinter'
objectData={objectFormState.objectData} objectData={objectData}
disabled={objectFormState.loading} disabled={loading}
/> />
</Space> </Space>
<LockIndicator lock={objectFormState.lock} /> <LockIndicator lock={lock} />
</Space> </Space>
<Space> <Space>
<EditButtons <EditButtons
isEditing={objectFormState.isEditing} isEditing={isEditing}
handleUpdate={() => { handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit') callAction('finishEdit')
}} }}
cancelEditing={() => { cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit') callAction('cancelEdit')
}} }}
startEditing={() => { startEditing={() => {
actionHandlerRef.current.callAction('edit') callAction('edit')
}} }}
editLoading={objectFormState.editLoading} editLoading={editLoading}
formValid={objectFormState.formValid} formValid={formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading} disabled={lock?.locked || loading}
loading={objectFormState.editLoading} loading={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) => updateCollapseState('info', expanded)} onToggle={(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 />}
@ -161,21 +155,22 @@ 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) => updateCollapseState('notes', expanded)} onToggle={(expanded) =>
updateCollapseState('notes', expanded)
}
collapseKey='notes' collapseKey='notes'
> >
<Card> <Card>
<NotesPanel _id={documentPrinterId} type='documentPrinter' /> <NotesPanel
_id={documentPrinterId}
type='documentPrinter'
/>
</Card> </Card>
</InfoCollapse> </InfoCollapse>
@ -188,7 +183,7 @@ const DocumentPrinterInfo = () => {
} }
collapseKey='auditLogs' collapseKey='auditLogs'
> >
{objectFormState.loading ? ( {loading ? (
<InfoCollapsePlaceholder /> <InfoCollapsePlaceholder />
) : ( ) : (
<ObjectTable <ObjectTable
@ -201,7 +196,11 @@ const DocumentPrinterInfo = () => {
</Flex> </Flex>
</div> </div>
</Flex> </Flex>
</> )}
</ActionHandler>
)
}}
</ObjectForm>
) )
} }

View File

@ -1,85 +0,0 @@
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

View File

@ -1,9 +1,19 @@
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 WizardView from '../../common/WizardView' import NewObjectButtons from '../../common/NewObjectButtons'
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'}
@ -59,18 +69,44 @@ const NewDocumentTemplate = ({ onOk }) => {
) )
} }
] ]
return ( return (
<WizardView <Flex gap='middle'>
steps={steps} {!isMobile && (
loading={submitLoading} <div style={{ minWidth: '160px' }}>
formValid={formValid} <Steps
title='New Document Template' current={currentStep}
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>

View File

@ -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, Modal } from 'antd' import { Space, Flex, Card, Splitter, Divider } 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,9 +28,6 @@ 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)
@ -60,9 +57,6 @@ const ControlPrinter = () => {
collapseState.movement collapseState.movement
) )
const [loadFilamentStockOpen, setLoadFilamentStockOpen] = useState(false)
const [unloadFilamentStockOpen, setUnloadFilamentStockOpen] = useState(false)
useEffect(() => { useEffect(() => {
setSideBarVisible( setSideBarVisible(
collapseState.temperature || collapseState.temperature ||
@ -128,39 +122,6 @@ 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
} }
} }
@ -185,7 +146,6 @@ const ControlPrinter = () => {
) )
return ( return (
<>
<Flex <Flex
gap='large' gap='large'
vertical='true' vertical='true'
@ -312,15 +272,7 @@ 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'
@ -381,7 +333,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={() => {}}
> >
{({ {({
@ -418,12 +370,10 @@ const ControlPrinter = () => {
updateCollapseState('filamentStock', expanded) updateCollapseState('filamentStock', expanded)
} }
> >
{objectFormState.objectData?.currentFilamentStock {objectFormState.objectData?.currentFilamentStock?._id ? (
?._id ? (
<ObjectForm <ObjectForm
id={ id={
objectFormState.objectData.currentFilamentStock objectFormState.objectData.currentFilamentStock._id
._id
} }
type='filamentStock' type='filamentStock'
onStateChange={() => {}} onStateChange={() => {}}
@ -488,35 +438,6 @@ 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>
</>
) )
} }

View File

@ -108,14 +108,7 @@ const DocumentPrintButton = ({
onCancel={() => setNewDocumentJobOpen(false)} onCancel={() => setNewDocumentJobOpen(false)}
footer={null} footer={null}
destroyOnHidden={true} destroyOnHidden={true}
width={{ width={900}
xs: '100%',
sm: '100%',
md: '100%',
lg: '90%',
xl: '80%',
xxl: '80%'
}}
> >
<NewDocumentJob <NewDocumentJob
onOk={() => { onOk={() => {

View File

@ -9,8 +9,7 @@ const NewObjectButtons = ({
onSubmit, onSubmit,
formValid, formValid,
submitLoading, submitLoading,
submitText = 'Done', submitText = 'Done'
disabled = false
}) => { }) => {
return ( return (
<Flex justify='end'> <Flex justify='end'>
@ -25,18 +24,14 @@ const NewObjectButtons = ({
) : null} ) : null}
{currentStep < totalSteps - 1 ? ( {currentStep < totalSteps - 1 ? (
<Button <Button type='primary' disabled={!formValid} onClick={onNext}>
type='primary'
disabled={!formValid || disabled}
onClick={onNext}
>
Next Next
</Button> </Button>
) : ( ) : (
<Button <Button
type='primary' type='primary'
loading={submitLoading} loading={submitLoading}
disabled={!formValid || disabled} disabled={!formValid}
onClick={onSubmit} onClick={onSubmit}
> >
{submitText} {submitText}
@ -54,8 +49,7 @@ 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

View File

@ -138,9 +138,8 @@ const ObjectForm = forwardRef(
return computedValues return computedValues
}, []) }, [])
// Validate form on change (debounced to avoid heavy work on every keystroke) // Validate form on change
useEffect(() => { useEffect(() => {
const timeoutId = setTimeout(() => {
form form
.validateFields({ validateOnly: true }) .validateFields({ validateOnly: true })
.then(() => { .then(() => {
@ -151,16 +150,12 @@ const ObjectForm = forwardRef(
}) })
}) })
.catch(() => { .catch(() => {
setFormValid(false)
onStateChange({ onStateChange({
formValid: false, formValid: true,
objectData: { ...serverObjectData, ...form.getFieldsValue() } objectData: { ...serverObjectData, ...form.getFieldsValue() }
}) })
}) })
}, 150) }, [form, formUpdateValues])
return () => clearTimeout(timeoutId)
}, [form, formUpdateValues, onStateChange, serverObjectData])
// Cleanup on unmount // Cleanup on unmount
useEffect(() => { useEffect(() => {
@ -257,14 +252,9 @@ const ObjectForm = forwardRef(
updateLockEventHandler updateLockEventHandler
]) ])
// Debounce objectData updates sent to parent to limit re-renders
useEffect(() => { useEffect(() => {
const timeoutId = setTimeout(() => {
onStateChange({ objectData }) onStateChange({ objectData })
}, 150) }, [objectData])
return () => clearTimeout(timeoutId)
}, [objectData, onStateChange])
const startEditing = () => { const startEditing = () => {
setIsEditing(true) setIsEditing(true)
@ -376,19 +366,10 @@ const ObjectForm = forwardRef(
model model
) )
// Update form with computed values if any were calculated and they changed // Update form with computed values if any were calculated
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 }

View File

@ -81,8 +81,6 @@ const ObjectProperty = ({
minimal = false, minimal = false,
previewOpen = false, previewOpen = false,
showPreview = true, showPreview = true,
options = [],
roundNumber = false,
showHyperlink, showHyperlink,
...rest ...rest
}) => { }) => {
@ -168,18 +166,6 @@ 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':
@ -248,15 +234,10 @@ 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' ? roundedValue : value} {typeof value === 'number' ? value.toFixed(2) : value}
{suffix} {suffix}
</Text> </Text>
) )
@ -572,17 +553,6 @@ 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}>
@ -803,8 +773,7 @@ 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

View File

@ -144,7 +144,6 @@ 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,
@ -279,11 +278,8 @@ const ObjectSelect = ({
handleFetchObjectsProperties() handleFetchObjectsProperties()
setInitialized(true) setInitialized(true)
} }
if (value == null) {
setTreeSelectValue(null)
setInitialized(true)
}
} }
handleValue() handleValue()
}, [ }, [
value, value,
@ -307,13 +303,8 @@ 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])

View File

@ -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 && lazyLoading == false) { if (hasMore) {
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, lazyLoading]) }, [pages, createSkeletonData, fetchData, hasMore])
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 && lazyLoading == false) { if (prevPage > 0) {
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, lazyLoading]) }, [pages, createSkeletonData, fetchData])
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: isMobile ? undefined : fixed, fixed: 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: 20 + rowActions.length * 30, // Adjust width based on number of actions width: 80 + rowActions.length * 40, // Adjust width based on number of actions
render: (record) => { render: (record) => {
return renderActions(record) return renderActions(record)
} }

View File

@ -17,8 +17,7 @@ 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 (
@ -32,7 +31,9 @@ const ObjectTypeSelect = ({
allowClear={allowClear} allowClear={allowClear}
disabled={disabled} disabled={disabled}
filterOption={(input, option) => filterOption={(input, option) =>
option.searchText?.includes(input.toLowerCase()) ?? false option.label.props.children[1].props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
} }
options={options} options={options}
/> />

View File

@ -4,13 +4,6 @@ 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
@ -23,14 +16,10 @@ const StateDisplay = ({ state, showProgress = true, showState = true }) => {
<StateTag state={currentState.type} /> <StateTag state={currentState.type} />
</Space> </Space>
)} )}
{showProgress && {showProgress && currentState?.progress && currentState?.progress > 0 ? (
loadingProgressTypes.includes(currentState.type) &&
currentState?.progress &&
currentState?.progress > 0 ? (
<Progress <Progress
percent={Math.round(currentState.progress * 100)} percent={Math.round(currentState.progress * 100)}
status={currentState.type === 'used' ? '' : 'active'} status='active'
strokeColor={currentState.type === 'used' ? 'orange' : ''}
style={{ width: '150px', marginBottom: '2px' }} style={{ width: '150px', marginBottom: '2px' }}
/> />
) : null} ) : null}

View File

@ -56,9 +56,9 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
status = 'success' status = 'success'
text = 'Ready' text = 'Ready'
break break
case 'new': case 'unconsumed':
status = 'success' status = 'success'
text = 'New' text = 'Unconsumed'
break break
case 'error': case 'error':
status = 'error' status = 'error'
@ -80,10 +80,6 @@ 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'

View File

@ -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, Select } from 'antd' import { Flex, Button, Input } 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,26 +14,22 @@ const TemplatePreview = ({
isEditing, isEditing,
onTestObjectOpen, onTestObjectOpen,
onPreviewMessage, onPreviewMessage,
showTestObject = false, showTestObject = false
showPreviewSwitch = true
}) => { }) => {
const iframeRef = useRef(null) const iframeRef = useRef(null)
const { fetchTemplatePreview, fetchTemplatePDF } = const { fetchTemplatePreview } = useContext(ApiServerContext)
useContext(ApiServerContext) const [previewContent, setPreviewContent] = useState('')
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 updatePreviewContentHTML = (html) => { const updatePreviewContent = (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
setPreviewContentHTML(html) setPreviewContent(html)
// Restore scroll position after iframe loads new content // Restore scroll position after iframe loads new content
const handleLoad = () => { const handleLoad = () => {
@ -44,23 +40,6 @@ 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)
@ -75,7 +54,7 @@ const TemplatePreview = ({
// Handle error through parent component // Handle error through parent component
onPreviewMessage(result.error, true) onPreviewMessage(result.error, true)
} else { } else {
updatePreviewContentHTML(result.html) updatePreviewContent(result.html)
onPreviewMessage('No issues found.', false) onPreviewMessage('No issues found.', false)
} }
} }
@ -87,13 +66,9 @@ 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%' }}>
@ -123,51 +98,34 @@ 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' }}
disabled={loading || reloadLoading || previewType == 'PDF'} loading={loading || reloadLoading}
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={previewType == 'HTML' ? previewContentHTML : undefined} srcDoc={previewContent}
src={previewType == 'PDF' ? pdfBlob : undefined}
frameBorder='0' frameBorder='0'
style={{ style={{
width: '100%', width: '100%',
@ -188,8 +146,7 @@ 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

View File

@ -1,15 +1,7 @@
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 { import { Typography, Flex, Steps, Divider, Progress } from 'antd'
Typography,
Flex,
Steps,
Divider,
Progress,
Button,
Dropdown
} from 'antd'
import NewObjectButtons from './NewObjectButtons' import NewObjectButtons from './NewObjectButtons'
const { Title } = Typography const { Title } = Typography
@ -23,10 +15,7 @@ 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 })
@ -37,7 +26,7 @@ const WizardView = ({
sideBar != null ? ( sideBar != null ? (
sideBar sideBar
) : ( ) : (
<div style={{ minWidth: sideBarGrow == true ? '100%' : '160px' }}> <div style={{ minWidth: '160px' }}>
<Steps <Steps
current={currentStep} current={currentStep}
items={steps} items={steps}
@ -56,13 +45,7 @@ const WizardView = ({
vertical vertical
justify='space-between' justify='space-between'
gap={'middle'} gap={'middle'}
style={ style={{ width: '100%' }}
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 }}>
@ -80,37 +63,8 @@ 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)}
@ -133,12 +87,9 @@ 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

View File

@ -308,14 +308,9 @@ 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 {
@ -534,7 +529,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, objectType: type }) socketRef.current.emit('subscribe_lock', { id: id, type: 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
@ -858,53 +853,6 @@ 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) {
@ -994,22 +942,6 @@ 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={{
@ -1035,14 +967,11 @@ const ApiServerProvider = ({ children }) => {
showError, showError,
fetchFileContent, fetchFileContent,
fetchTemplatePreview, fetchTemplatePreview,
fetchTemplatePDF,
fetchNotes, fetchNotes,
downloadTemplatePDF,
fetchHostOTP, fetchHostOTP,
sendObjectAction, sendObjectAction,
uploadFile, uploadFile,
flushFile, flushFile
formatFileName
}} }}
> >
{contextHolder} {contextHolder}

View File

@ -6,9 +6,9 @@ const config = {
logLevel: 'trace' logLevel: 'trace'
}, },
production: { production: {
backendUrl: 'https://dev.tombutcher.work/api', backendUrl: 'http://192.168.68.53:8080',
printServerUrl: 'ws://192.168.68.53:8081', printServerUrl: 'ws://192.168.68.53:8081',
apiServerUrl: 'https://dev-wss.tombutcher.work', apiServerUrl: 'ws://192.168.68.53:9090',
logLevel: 'error' logLevel: 'error'
} }
} }

View File

@ -2,12 +2,11 @@ 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: 'DJB', prefix: 'DSZ',
icon: DocumentJobIcon, icon: DocumentJobIcon,
actions: [ actions: [
{ {
@ -61,9 +60,7 @@ export const DocumentJob = {
columnWidth: 200, columnWidth: 200,
columnFixed: 'left', columnFixed: 'left',
value: (objectData) => { value: (objectData) => {
if (objectData?.createdAt == undefined) { return `${objectData?.documentTemplate?.name || 'No template'} (${objectData?.object?.name || 'No name'})`
return `${objectData?.documentTemplate?.name || 'No template'} ${dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')} (${objectData?.object?.name || objectData?.object?._id})`
}
} }
}, },
{ {
@ -72,14 +69,6 @@ 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',
@ -97,15 +86,6 @@ 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',
@ -121,13 +101,6 @@ export const DocumentJob = {
} }
} }
}, },
{
name: 'documentTemplate._id',
label: 'Template ID',
type: 'id',
showHyperlink: true,
objectType: 'documentTemplate'
},
{ {
name: 'documentPrinter', name: 'documentPrinter',
label: 'Printer', label: 'Printer',
@ -141,13 +114,6 @@ export const DocumentJob = {
online: true online: true
} }
} }
},
{
name: 'documentPrinter._id',
label: 'Printer ID',
type: 'id',
showHyperlink: true,
objectType: 'documentPrinter'
} }
] ]
} }

View File

@ -74,88 +74,52 @@ export const DocumentPrinter = {
readOnly: true readOnly: true
}, },
{ {
name: 'state', name: 'documentSize',
label: 'Status', label: 'Document Size',
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: 'currentDocumentSize._id', name: 'documentSize._id',
label: 'Current Document Size ID', label: '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'
} }
] ]
} }

View File

@ -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: 'startQueue', name: 'Start',
label: 'Start Queue', label: 'Start',
icon: PlayCircleIcon, icon: PlayCircleIcon,
disabled: (objectData) => { disabled: (objectData) => {
console.log(objectData?.subJobs?.length) console.log(objectData?.subJobs?.length)
@ -109,60 +109,28 @@ 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: 'pauseJob', name: 'pause',
label: 'Pause Job', label: 'Pause',
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=pauseJob` `/dashboard/production/printers/control?printerId=${_id}&action=pauseQueue`
}, },
{ {
name: 'resumeJob', name: 'Stop',
label: 'Resume Job', label: 'Stop',
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=cancelJob` `/dashboard/production/printers/control?printerId=${_id}&action=stopQueue`
}
]
},
{
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`
} }
] ]
} }
@ -331,14 +299,6 @@ 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
} }
] ]
} }