Compare commits
No commits in common. "05d7864da8222397d9beb8d3913abb690e6c17e8" and "59632c30605d5c980ec1dc14003d3d836d4c6f69" have entirely different histories.
05d7864da8
...
59632c3060
@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.965553,0,0,0.965553,2.707286,3.96965)">
|
||||
<path d="M0,38.289C0,42.92 3.792,46.713 8.424,46.713C13.066,46.713 16.88,42.94 16.88,38.289C16.88,33.647 13.066,29.864 8.424,29.864C3.792,29.864 0,33.647 0,38.289ZM4.47,38.289C4.47,36.077 6.234,34.334 8.445,34.334C10.687,34.334 12.41,36.077 12.41,38.289C12.41,40.53 10.687,42.243 8.445,42.243C6.234,42.243 4.47,40.53 4.47,38.289ZM8.445,0C6.681,0 5.129,1.453 5.129,3.267L5.129,32.475L11.741,32.475L11.741,3.267C11.741,1.443 10.259,0 8.445,0ZM8.445,58.061C10.279,58.061 11.741,56.628 11.741,54.889L11.741,44.362L5.129,44.362L5.129,54.889C5.129,56.618 6.652,58.061 8.445,58.061ZM21.887,19.56C21.887,24.182 25.701,27.994 30.333,27.994C34.974,27.994 38.767,24.202 38.767,19.56C38.767,14.928 34.974,11.136 30.333,11.136C25.701,11.136 21.887,14.928 21.887,19.56ZM26.378,19.56C26.378,17.37 28.142,15.606 30.333,15.606C32.574,15.606 34.297,17.37 34.297,19.56C34.297,21.802 32.574,23.525 30.333,23.525C28.142,23.525 26.378,21.802 26.378,19.56ZM30.333,0C28.569,0 27.038,1.453 27.038,3.171L27.038,13.639L33.649,13.639L33.649,3.171C33.649,1.443 32.136,0 30.333,0ZM30.333,58.061C32.166,58.061 33.649,56.628 33.649,54.804L33.649,25.244L27.038,25.244L27.038,54.804C27.038,56.618 28.539,58.061 30.333,58.061ZM43.817,38.289C43.817,42.92 47.61,46.713 52.241,46.713C56.883,46.713 60.675,42.92 60.675,38.289C60.675,33.647 56.883,29.864 52.241,29.864C47.61,29.864 43.817,33.647 43.817,38.289ZM48.287,38.289C48.287,36.077 50.051,34.334 52.241,34.334C54.504,34.334 56.206,36.077 56.206,38.289C56.206,40.53 54.504,42.243 52.241,42.243C50.051,42.243 48.287,40.53 48.287,38.289ZM52.241,0C50.477,0 48.925,1.453 48.925,3.267L48.925,32.592L55.536,32.592L55.536,3.267C55.536,1.443 54.045,0 52.241,0ZM52.241,58.061C54.075,58.061 55.536,56.628 55.536,54.889L55.536,44.054L48.925,44.054L48.925,54.889C48.925,56.618 50.448,58.061 52.241,58.061Z" style="fill-rule:nonzero;"/>
|
||||
<g transform="matrix(0.707791,0,0,0.707791,-3.35696e-07,11.571)">
|
||||
<path d="M13.734,57.726L76.694,57.726C86.55,57.726 91.489,51.584 90.229,41.164L86.837,12.946C85.843,4.532 81.097,0 73.389,0L17.039,0C9.33,0 4.579,4.532 3.585,12.946L0.194,41.164C-1.067,51.584 3.872,57.726 13.734,57.726ZM13.734,51.604C8.264,51.604 5.532,47.716 6.33,41.05L9.645,13.674C10.242,8.783 12.818,6.122 17.039,6.122L73.389,6.122C77.61,6.122 80.18,8.783 80.757,13.674L84.066,41.05C84.896,47.741 82.159,51.604 76.694,51.604L13.734,51.604Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.707791,0,0,0.707791,-3.35696e-07,11.571)">
|
||||
<path d="M24.535,37.579C25.826,37.579 26.851,36.966 26.851,36.089L26.851,27.451C30.02,26.514 32.35,23.524 32.35,20.015C32.35,15.714 28.836,12.194 24.535,12.194C20.208,12.194 16.708,15.739 16.708,20.015C16.708,23.55 19.044,26.519 22.244,27.502L22.244,36.089C22.244,36.966 23.249,37.579 24.535,37.579ZM24.541,44.44C31.002,44.44 35.524,40.961 35.524,36.058C35.524,32.661 33.342,29.971 29.911,28.613L29.911,33.011C30.937,33.758 31.546,34.81 31.546,36.058C31.546,38.902 28.677,40.891 24.541,40.891C20.399,40.891 17.498,38.902 17.498,36.058C17.498,34.784 18.127,33.727 19.185,32.979L19.185,28.619C15.707,29.939 13.525,32.661 13.525,36.058C13.525,40.961 18.079,44.44 24.541,44.44ZM42.796,26.929C45.401,26.929 47.471,24.864 47.471,22.286C47.471,19.687 45.401,17.611 42.796,17.611C40.229,17.611 38.147,19.687 38.147,22.286C38.147,24.864 40.229,26.929 42.796,26.929ZM56.127,26.929C58.701,26.929 60.802,24.864 60.802,22.286C60.802,19.687 58.701,17.611 56.127,17.611C53.529,17.611 51.452,19.687 51.452,22.286C51.452,24.864 53.529,26.929 56.127,26.929ZM69.385,26.929C71.964,26.929 74.06,24.864 74.06,22.286C74.06,19.687 71.964,17.611 69.385,17.611C66.817,17.611 64.71,19.687 64.71,22.286C64.71,24.864 66.817,26.929 69.385,26.929ZM47.112,40.193C49.691,40.193 51.762,38.128 51.762,35.549C51.762,32.95 49.691,30.874 47.112,30.874C44.519,30.874 42.437,32.95 42.437,35.549C42.437,38.128 44.519,40.193 47.112,40.193ZM60.418,40.193C63.017,40.193 65.093,38.128 65.093,35.549C65.093,32.95 63.017,30.874 60.418,30.874C57.845,30.874 55.769,32.95 55.769,35.549C55.769,38.128 57.845,40.193 60.418,40.193ZM73.707,40.193C76.306,40.193 78.382,38.128 78.382,35.549C78.382,32.95 76.306,30.874 73.707,30.874C71.134,30.874 69.057,32.95 69.057,35.549C69.057,38.128 71.134,40.193 73.707,40.193Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.8 KiB |
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M25,54.028C25,54.887 24.545,55.681 23.804,56.116C23.064,56.55 22.148,56.56 21.398,56.141C16.479,53.393 8.262,48.803 8.262,48.803C5.729,47.378 4.441,45.879 4.441,42.127L4.441,20.65C4.441,17.814 5.489,16.02 7.877,14.692L26.541,4.215C29.828,2.367 33.176,2.367 36.463,4.215L55.149,14.692C57.515,16.02 58.563,17.814 58.563,20.65L58.563,22.637C58.563,23.942 57.505,25 56.2,25L56.198,25C55.571,25 54.97,24.751 54.527,24.308C54.084,23.865 53.835,23.264 53.835,22.637L53.835,22.284C53.835,22.284 51.807,23.429 50.385,24.232C49.494,24.736 48.488,25 47.465,25L40.475,25C39.454,25 38.45,24.737 37.56,24.235C33.115,21.731 19.274,13.934 19.274,13.934L11.856,18.15C11.856,18.15 21.408,23.505 25.75,25.94C26.238,26.214 26.59,26.68 26.72,27.224C26.849,27.769 26.746,28.343 26.434,28.808C26.264,28.997 26.133,29.214 26.013,29.437C25.763,29.906 25.335,30.254 24.826,30.402C24.316,30.551 23.768,30.487 23.306,30.226C18.993,27.833 9.169,22.284 9.169,22.284L9.169,41.664C9.169,43.059 9.681,43.919 11.137,44.722C11.137,44.722 20.377,49.94 23.77,51.856C24.53,52.285 25,53.091 25,53.964L25,54.028ZM43.972,22.202L24.257,11.165L28.656,8.675C30.587,7.587 32.407,7.556 34.365,8.675L51.17,18.15L43.972,22.202Z"/>
|
||||
<g transform="matrix(0.58577,0,0,0.58577,29,29)">
|
||||
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
|
||||
<g transform="matrix(0.664312,0,0,0.664312,5.121464,12.135807)">
|
||||
<path d="M37.594,52.969C39.062,52.969 40.625,52.281 41.875,51L61.844,31.094C62.938,30 63.656,28.188 63.656,26.5C63.656,24.812 62.938,23 61.844,21.906L41.875,1.969C40.625,0.688 39.062,0 37.594,0C33.812,0 31.406,2.562 31.406,5.906C31.406,7.875 32.312,9.25 33.5,10.406L40.5,17.344L50.219,26.5L40.5,35.656L33.5,42.562C32.312,43.688 31.406,45.094 31.406,47.062C31.406,50.406 33.812,52.969 37.594,52.969ZM1.485,32.781L37.75,32.781L51.969,32.094C55.531,31.938 57.906,29.844 57.906,26.5C57.906,23.156 55.531,21.062 51.969,20.906L37.75,20.219L1.485,20.219C-2.515,20.219 -5.14,22.719 -5.14,26.5C-5.14,30.281 -2.515,32.781 1.485,32.781Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@ -318,30 +318,3 @@ body {
|
||||
.ant-badge.ant-badge-status {
|
||||
line-height: 18.5px;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-vertical {
|
||||
right: -16px;
|
||||
width: 8px !important;
|
||||
}
|
||||
|
||||
.simplebar-scrollbar:before {
|
||||
background: #78787854 !important;
|
||||
}
|
||||
|
||||
.printer-alerts-display-popover .ant-popover-inner {
|
||||
padding: 0 !important;
|
||||
margin: 0 24px !important;
|
||||
}
|
||||
|
||||
.child-table-rollups *::-webkit-scrollbar:horizontal {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.rollup-table .ant-table-container {
|
||||
border-start-start-radius: 0px !important;
|
||||
border-start-end-radius: 0px !important;
|
||||
}
|
||||
|
||||
.rollup-table .ant-table {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
|
||||
@ -59,7 +59,6 @@
|
||||
"react-responsive": "^10.0.1",
|
||||
"react-router-dom": "^7.8.2",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"simplebar-react": "^3.3.2",
|
||||
"socket.io-client": "*",
|
||||
"standard": "^17.1.2",
|
||||
"styled-components": "^6.1.19",
|
||||
|
||||
11
src/App.jsx
11
src/App.jsx
@ -13,7 +13,6 @@ import '../assets/stylesheets/App.css'
|
||||
import { PrintServerProvider } from './components/Dashboard/context/PrintServerContext.jsx'
|
||||
import { AuthProvider } from './components/Dashboard/context/AuthContext.jsx'
|
||||
import { SpotlightProvider } from './components/Dashboard/context/SpotlightContext.jsx'
|
||||
import { ActionsModalProvider } from './components/Dashboard/context/ActionsModalContext.jsx'
|
||||
|
||||
import {
|
||||
ThemeProvider,
|
||||
@ -22,7 +21,6 @@ import {
|
||||
import AppError from './components/App/AppError'
|
||||
import { ApiServerProvider } from './components/Dashboard/context/ApiServerContext.jsx'
|
||||
import { ElectronProvider } from './components/Dashboard/context/ElectronContext.jsx'
|
||||
import { MessageProvider } from './components/Dashboard/context/MessageContext.jsx'
|
||||
import AuthCallback from './components/App/AuthCallback.jsx'
|
||||
|
||||
import {
|
||||
@ -55,8 +53,6 @@ const AppContent = () => {
|
||||
<PrintServerProvider>
|
||||
<ApiServerProvider>
|
||||
<SpotlightProvider>
|
||||
<ActionsModalProvider>
|
||||
<MessageProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path='/'
|
||||
@ -71,10 +67,7 @@ const AppContent = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/callback'
|
||||
element={<AuthCallback />}
|
||||
/>
|
||||
<Route path='/auth/callback' element={<AuthCallback />} />
|
||||
<Route
|
||||
path='/dashboard'
|
||||
element={
|
||||
@ -96,8 +89,6 @@ const AppContent = () => {
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</MessageProvider>
|
||||
</ActionsModalProvider>
|
||||
</SpotlightProvider>
|
||||
</ApiServerProvider>
|
||||
</PrintServerProvider>
|
||||
|
||||
@ -202,7 +202,7 @@ const LoadFilamentStock = ({
|
||||
) : null}
|
||||
|
||||
{targetTemperature > 0 &&
|
||||
currentTemperature >= targetTemperature - 2 &&
|
||||
currentTemperature >= targetTemperature &&
|
||||
filamentSensorDetected == false ? (
|
||||
<Alert
|
||||
message={'Insert filament to continue'}
|
||||
|
||||
@ -1,101 +1,119 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Form, Input, Button, Space, Select, InputNumber } from 'antd'
|
||||
import axios from 'axios'
|
||||
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 NewObjectForm from '../../common/NewObjectForm'
|
||||
import NewObjectButtons from '../../common/NewObjectButtons'
|
||||
|
||||
const { Title } = Typography
|
||||
import config from '../../../../config'
|
||||
|
||||
const NewPartStock = ({ onOk, reset }) => {
|
||||
const [currentStep, setCurrentStep] = useState(0)
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
const [form] = Form.useForm()
|
||||
const [parts, setParts] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
return (
|
||||
<NewObjectForm
|
||||
type={'partStock'}
|
||||
reset={reset}
|
||||
defaultValues={{ state: { type: 'new' } }}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
useEffect(() => {
|
||||
// Reset form when reset prop changes
|
||||
if (reset) {
|
||||
form.resetFields()
|
||||
}
|
||||
}, [reset, form])
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch parts for the select dropdown
|
||||
const fetchParts = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${config.backendUrl}/parts`, {
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
withCredentials: true
|
||||
})
|
||||
setParts(response.data)
|
||||
} catch (error) {
|
||||
console.error('Error fetching parts:', error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchParts()
|
||||
}, [])
|
||||
|
||||
const onFinish = async (values) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
await axios.post(
|
||||
`${config.backendUrl}/partstocks`,
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='partStock'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
initial={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
part: values.part,
|
||||
startingLots: values.startingLots,
|
||||
currentLots: values.startingLots, // Initially current lots equals starting lots
|
||||
notes: values.notes
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='partStock'
|
||||
column={1}
|
||||
bordered={false}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
withCredentials: true
|
||||
}
|
||||
]
|
||||
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 gap='middle' style={{ flexGrow: 1 }}>
|
||||
<Title level={2} style={{ margin: 0 }}>
|
||||
New Part Stock
|
||||
</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={() => {
|
||||
handleSubmit()
|
||||
onOk()
|
||||
}}
|
||||
formValid={formValid}
|
||||
submitLoading={submitLoading}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}}
|
||||
</NewObjectForm>
|
||||
onOk()
|
||||
} catch (error) {
|
||||
console.error('Error creating part stock:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
layout='vertical'
|
||||
onFinish={onFinish}
|
||||
style={{ maxWidth: '100%' }}
|
||||
>
|
||||
<Form.Item
|
||||
name='part'
|
||||
label='Part'
|
||||
rules={[{ required: true, message: 'Please select a part' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder='Select a part'
|
||||
options={parts.map((part) => ({
|
||||
value: part._id,
|
||||
label: part.name
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name='startingLots'
|
||||
label='Starting Lots'
|
||||
rules={[
|
||||
{ required: true, message: 'Please enter the starting lots' },
|
||||
{ type: 'number', min: 1, message: 'Lots must be at least 1' }
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
placeholder='Enter starting lots'
|
||||
min={1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name='notes' label='Notes'>
|
||||
<Input.TextArea
|
||||
placeholder='Enter any additional notes'
|
||||
autoSize={{ minRows: 3, maxRows: 6 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type='primary' htmlType='submit' loading={loading}>
|
||||
Create Part Stock
|
||||
</Button>
|
||||
<Button onClick={() => form.resetFields()}>Reset</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
@ -104,4 +122,8 @@ NewPartStock.propTypes = {
|
||||
reset: PropTypes.bool
|
||||
}
|
||||
|
||||
NewPartStock.defaultProps = {
|
||||
reset: false
|
||||
}
|
||||
|
||||
export default NewPartStock
|
||||
|
||||
@ -1,209 +0,0 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
import useCollapseState from '../../hooks/useCollapseState.js'
|
||||
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
||||
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||
import ViewButton from '../../common/ViewButton.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||
import EditButtons from '../../common/EditButtons.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
|
||||
const log = loglevel.getLogger('PartStockInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
|
||||
const PartStockInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const partStockId = new URLSearchParams(location.search).get('partStockId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'PartStockInfo',
|
||||
{
|
||||
info: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: true
|
||||
}
|
||||
)
|
||||
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
|
||||
const actions = {
|
||||
reload: () => {
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
return true
|
||||
},
|
||||
edit: () => {
|
||||
objectFormRef?.current.startEditing()
|
||||
return false
|
||||
},
|
||||
cancelEdit: () => {
|
||||
objectFormRef?.current.cancelEditing()
|
||||
return true
|
||||
},
|
||||
finishEdit: () => {
|
||||
objectFormRef?.current.handleUpdate()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{
|
||||
maxHeight: '100%',
|
||||
minHeight: 0
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='partStock'
|
||||
id={partStockId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Part Stock Information' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='partStock'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={objectFormState.isEditing}
|
||||
handleUpdate={() => {
|
||||
actionHandlerRef.current.callAction('finishEdit')
|
||||
}}
|
||||
cancelEditing={() => {
|
||||
actionHandlerRef.current.callAction('cancelEdit')
|
||||
}}
|
||||
startEditing={() => {
|
||||
actionHandlerRef.current.callAction('edit')
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<ScrollBox>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Part Stock Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={partStockId}
|
||||
type='partStock'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
onStateChange={(state) => {
|
||||
console.log('Got edit form state change', state)
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => {
|
||||
return (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
type='partStock'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
content: false,
|
||||
testObject: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={partStockId} type='partStock' />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Audit Logs'
|
||||
icon={<AuditLogIcon />}
|
||||
active={collapseState.auditLogs}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('auditLogs', expanded)
|
||||
}
|
||||
collapseKey='auditLogs'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{ 'parent._id': partStockId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PartStockInfo
|
||||
@ -1,34 +1,143 @@
|
||||
// src/stockAudits.js
|
||||
import { useState, useContext, useRef, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Button, Flex, Space, message, Dropdown, Typography } from 'antd'
|
||||
|
||||
import { useState, useRef } from 'react'
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
import { PrintServerContext } from '../context/PrintServerContext'
|
||||
|
||||
import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
|
||||
|
||||
import NewStockAudit from './StockAudits/NewStockAudit'
|
||||
import IdDisplay from '../common/IdDisplay'
|
||||
import StockAuditIcon from '../../Icons/StockAuditIcon'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ListIcon from '../../Icons/ListIcon'
|
||||
import GridIcon from '../../Icons/GridIcon'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
import ColumnViewButton from '../common/ColumnViewButton'
|
||||
|
||||
import config from '../../../config'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const StockAudits = () => {
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
const navigate = useNavigate()
|
||||
const { printServer } = useContext(PrintServerContext)
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
const tableRef = useRef()
|
||||
|
||||
const [newStockAuditOpen, setNewStockAuditOpen] = useState(false)
|
||||
const { authenticated } = useContext(AuthContext)
|
||||
|
||||
const [viewMode, setViewMode] = useViewMode('stockAudits')
|
||||
useEffect(() => {
|
||||
if (printServer && !initialized) {
|
||||
setInitialized(true)
|
||||
printServer.on('notify_stockaudit_update', (updateData) => {
|
||||
if (tableRef.current) {
|
||||
tableRef.current.updateData(updateData._id, updateData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
useColumnVisibility('stockAudits')
|
||||
return () => {
|
||||
if (printServer && initialized) {
|
||||
printServer.off('notify_stockaudit_update')
|
||||
}
|
||||
}
|
||||
}, [printServer, initialized])
|
||||
|
||||
const getStockAuditActionItems = (id) => {
|
||||
return {
|
||||
items: [
|
||||
{
|
||||
label: 'Info',
|
||||
key: 'info',
|
||||
icon: <InfoCircleIcon />
|
||||
}
|
||||
],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'info') {
|
||||
navigate(`/dashboard/inventory/stockaudits/info?stockAuditId=${id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: '',
|
||||
key: 'icon',
|
||||
width: 40,
|
||||
fixed: 'left',
|
||||
render: () => <StockAuditIcon />
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: '_id',
|
||||
key: 'id',
|
||||
width: 180,
|
||||
render: (text) => (
|
||||
<IdDisplay id={text} type={'stockaudit'} longId={false} />
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
render: (status) => <Text>{status}</Text>
|
||||
},
|
||||
{
|
||||
title: 'Created At',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
render: (createdAt) => {
|
||||
if (createdAt) {
|
||||
return <TimeDisplay dateTime={createdAt} />
|
||||
}
|
||||
return 'n/a'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Updated At',
|
||||
dataIndex: 'updatedAt',
|
||||
key: 'updatedAt',
|
||||
width: 180,
|
||||
render: (updatedAt) => {
|
||||
if (updatedAt) {
|
||||
return <TimeDisplay dateTime={updatedAt} />
|
||||
}
|
||||
return 'n/a'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
render: (text, record) => {
|
||||
return (
|
||||
<Space gap='small'>
|
||||
<Button
|
||||
icon={<InfoCircleIcon />}
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/dashboard/inventory/stockaudits/info?stockAuditId=${record._id}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Dropdown menu={getStockAuditActionItems(record._id)}>
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const actionItems = {
|
||||
items: [
|
||||
{
|
||||
label: 'New Stock audit',
|
||||
label: 'New Stock Audit',
|
||||
key: 'newStockAudit',
|
||||
icon: <PlusIcon />
|
||||
},
|
||||
@ -43,7 +152,8 @@ const StockAudits = () => {
|
||||
if (key === 'reloadList') {
|
||||
tableRef.current?.reload()
|
||||
} else if (key === 'newStockAudit') {
|
||||
setNewStockAuditOpen(true)
|
||||
// TODO: Implement new stock audit creation
|
||||
messageApi.info('New stock audit creation not implemented yet')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -52,54 +162,18 @@ const StockAudits = () => {
|
||||
<>
|
||||
<Flex vertical={'true'} gap='large'>
|
||||
{contextHolder}
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='small'>
|
||||
<Space>
|
||||
<Dropdown menu={actionItems}>
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
<ColumnViewButton
|
||||
type='stockAudit'
|
||||
loading={false}
|
||||
visibleState={columnVisibility}
|
||||
updateVisibleState={setColumnVisibility}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button
|
||||
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||
onClick={() =>
|
||||
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
visibleColumns={columnVisibility}
|
||||
type='stockAudit'
|
||||
cards={viewMode === 'cards'}
|
||||
columns={columns}
|
||||
url={`${config.backendUrl}/stockaudits`}
|
||||
authenticated={authenticated}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
open={newStockAuditOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={800}
|
||||
onCancel={() => {
|
||||
setNewStockAuditOpen(false)
|
||||
}}
|
||||
destroyOnHidden={true}
|
||||
>
|
||||
<NewStockAudit
|
||||
onOk={() => {
|
||||
setNewStockAuditOpen(false)
|
||||
messageApi.success('New stock audit created successfully.')
|
||||
tableRef.current?.reload()
|
||||
}}
|
||||
reset={newStockAuditOpen}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,107 +0,0 @@
|
||||
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 NewObjectForm from '../../common/NewObjectForm'
|
||||
import NewObjectButtons from '../../common/NewObjectButtons'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
const NewStockAudit = ({ onOk, reset }) => {
|
||||
const [currentStep, setCurrentStep] = useState(0)
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
|
||||
return (
|
||||
<NewObjectForm
|
||||
type={'stockAudit'}
|
||||
reset={reset}
|
||||
defaultValues={{ state: { type: 'new' } }}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='stockAudit'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
initial={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='stockAudit'
|
||||
column={1}
|
||||
bordered={false}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
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 gap='middle' style={{ flexGrow: 1 }}>
|
||||
<Title level={2} style={{ margin: 0 }}>
|
||||
New Stock audit
|
||||
</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={() => {
|
||||
handleSubmit()
|
||||
onOk()
|
||||
}}
|
||||
formValid={formValid}
|
||||
submitLoading={submitLoading}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}}
|
||||
</NewObjectForm>
|
||||
)
|
||||
}
|
||||
|
||||
NewStockAudit.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool
|
||||
}
|
||||
|
||||
export default NewStockAudit
|
||||
@ -1,207 +1,214 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import loglevel from 'loglevel'
|
||||
import config from '../../../../config.js'
|
||||
import useCollapseState from '../../hooks/useCollapseState.js'
|
||||
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
||||
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||
import ViewButton from '../../common/ViewButton.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||
import EditButtons from '../../common/EditButtons.jsx'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import { useEffect, useState, useContext } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
Card,
|
||||
Descriptions,
|
||||
Button,
|
||||
Space,
|
||||
message,
|
||||
Typography,
|
||||
Table,
|
||||
Tag
|
||||
} from 'antd'
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
LoadingOutlined,
|
||||
ClockCircleOutlined
|
||||
} from '@ant-design/icons'
|
||||
|
||||
const log = loglevel.getLogger('StockAuditInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
import { AuthContext } from '../../context/AuthContext'
|
||||
import IdDisplay from '../../common/IdDisplay'
|
||||
import TimeDisplay from '../../common/TimeDisplay'
|
||||
|
||||
import config from '../../../../config'
|
||||
import XMarkCircleIcon from '../../../Icons/XMarkCircleIcon'
|
||||
import CheckCircleIcon from '../../../Icons/CheckCircleIcon'
|
||||
|
||||
const { Text, Title } = Typography
|
||||
|
||||
const StockAuditInfo = () => {
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const navigate = useNavigate()
|
||||
const { authenticated } = useContext(AuthContext)
|
||||
|
||||
const [stockAudit, setStockAudit] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const stockAuditId = new URLSearchParams(location.search).get('stockAuditId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState(
|
||||
'StockAuditInfo',
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStockAudit = async () => {
|
||||
if (!stockAuditId) {
|
||||
messageApi.error('No stock audit ID provided')
|
||||
navigate('/dashboard/inventory/stockaudits')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${config.backendUrl}/stockaudits/${stockAuditId}`,
|
||||
{
|
||||
info: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: true
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
|
||||
const actions = {
|
||||
reload: () => {
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
return true
|
||||
},
|
||||
edit: () => {
|
||||
objectFormRef?.current.startEditing()
|
||||
return false
|
||||
},
|
||||
cancelEdit: () => {
|
||||
objectFormRef?.current.cancelEditing()
|
||||
return true
|
||||
},
|
||||
finishEdit: () => {
|
||||
objectFormRef?.current.handleUpdate()
|
||||
return true
|
||||
setStockAudit(response.data)
|
||||
setLoading(false)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
messageApi.error('Failed to fetch stock audit details')
|
||||
navigate('/dashboard/inventory/stockaudits')
|
||||
}
|
||||
}
|
||||
|
||||
if (authenticated) {
|
||||
fetchStockAudit()
|
||||
}
|
||||
}, [authenticated, stockAuditId, messageApi, navigate])
|
||||
|
||||
const getStatusTag = (status) => {
|
||||
switch (status?.toLowerCase()) {
|
||||
case 'completed':
|
||||
return (
|
||||
<Tag icon={<CheckCircleIcon />} color='success'>
|
||||
Completed
|
||||
</Tag>
|
||||
)
|
||||
case 'in_progress':
|
||||
return (
|
||||
<Tag icon={<ClockCircleOutlined />} color='processing'>
|
||||
In Progress
|
||||
</Tag>
|
||||
)
|
||||
case 'failed':
|
||||
return (
|
||||
<Tag icon={<XMarkCircleIcon />} color='error'>
|
||||
Failed
|
||||
</Tag>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<Tag icon={<ClockCircleOutlined />} color='default'>
|
||||
Unknown
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const auditItemsColumns = [
|
||||
{
|
||||
title: 'Item ID',
|
||||
dataIndex: '_id',
|
||||
key: 'id',
|
||||
width: 180,
|
||||
render: (text) => (
|
||||
<IdDisplay id={text} type={'stockaudititem'} longId={false} />
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Item Type',
|
||||
dataIndex: 'itemType',
|
||||
key: 'itemType',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: 'Expected Weight',
|
||||
dataIndex: 'expectedWeight',
|
||||
key: 'expectedWeight',
|
||||
width: 120,
|
||||
render: (weight) => `${weight.toFixed(2)}g`
|
||||
},
|
||||
{
|
||||
title: 'Actual Weight',
|
||||
dataIndex: 'actualWeight',
|
||||
key: 'actualWeight',
|
||||
width: 120,
|
||||
render: (weight) => `${weight.toFixed(2)}g`
|
||||
},
|
||||
{
|
||||
title: 'Difference',
|
||||
key: 'difference',
|
||||
width: 120,
|
||||
render: (_, record) => {
|
||||
const diff = record.actualWeight - record.expectedWeight
|
||||
return (
|
||||
<Text type={diff === 0 ? 'success' : 'danger'}>
|
||||
{diff.toFixed(2)}g
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
render: (status) => getStatusTag(status)
|
||||
}
|
||||
]
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<LoadingOutlined style={{ fontSize: 24 }} spin />
|
||||
<Text style={{ marginLeft: 16 }}>Loading stock audit details...</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!stockAudit) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{
|
||||
maxHeight: '100%',
|
||||
minHeight: 0
|
||||
}}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='stockAudit'
|
||||
id={stockAuditId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Stock Audit Information' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='stockAudit'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
{contextHolder}
|
||||
<Space direction='vertical' size='large' style={{ width: '100%' }}>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={objectFormState.isEditing}
|
||||
handleUpdate={() => {
|
||||
actionHandlerRef.current.callAction('finishEdit')
|
||||
}}
|
||||
cancelEditing={() => {
|
||||
actionHandlerRef.current.callAction('cancelEdit')
|
||||
}}
|
||||
startEditing={() => {
|
||||
actionHandlerRef.current.callAction('edit')
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
<Button
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate('/dashboard/inventory/stockaudits')}
|
||||
>
|
||||
Back to Stock Audits
|
||||
</Button>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<ScrollBox>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Stock Audit Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={stockAuditId}
|
||||
type='stockAudit'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
onStateChange={(state) => {
|
||||
console.log('Got edit form state change', state)
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => {
|
||||
return (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
type='stockAudit'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
content: false,
|
||||
testObject: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={stockAuditId} type='stockAudit' />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
<InfoCollapse
|
||||
title='Audit Logs'
|
||||
icon={<AuditLogIcon />}
|
||||
active={collapseState.auditLogs}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('auditLogs', expanded)
|
||||
}
|
||||
collapseKey='auditLogs'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{ 'parent._id': stockAuditId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
<Title level={4}>Stock Audit Details</Title>
|
||||
<Descriptions bordered>
|
||||
<Descriptions.Item label='ID'>
|
||||
<IdDisplay
|
||||
id={stockAudit._id}
|
||||
type={'stockaudit'}
|
||||
longId={true}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label='Status'>
|
||||
{getStatusTag(stockAudit.status)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label='Created At'>
|
||||
<TimeDisplay dateTime={stockAudit.createdAt} showSince={true} />
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label='Updated At'>
|
||||
<TimeDisplay dateTime={stockAudit.updatedAt} showSince={true} />
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
<Card title='Audit Items'>
|
||||
<Table
|
||||
dataSource={stockAudit.items || []}
|
||||
columns={auditItemsColumns}
|
||||
rowKey='_id'
|
||||
pagination={false}
|
||||
scroll={{ y: 'calc(100vh - 500px)' }}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -12,15 +12,13 @@ import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ProductIcon from '../../../Icons/ProductIcon.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||
import PartIcon from '../../../Icons/PartIcon.jsx'
|
||||
|
||||
const ProductInfo = () => {
|
||||
const location = useLocation()
|
||||
@ -120,6 +118,13 @@ const ProductInfo = () => {
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Product Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={productId}
|
||||
@ -131,47 +136,32 @@ const ProductInfo = () => {
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<Flex vertical gap={'large'}>
|
||||
<InfoCollapse
|
||||
title='Product Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('info', expanded)
|
||||
}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
isEditing={isEditing}
|
||||
type='product'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
parts: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Product Parts'
|
||||
icon={<PartIcon />}
|
||||
active={collapseState.parts}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('parts', expanded)
|
||||
}
|
||||
collapseKey='parts'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('product', 'parts')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
|
||||
<InfoCollapse
|
||||
title='Product Parts'
|
||||
icon={<ProductIcon />}
|
||||
active={collapseState.parts}
|
||||
onToggle={(expanded) => updateCollapseState('parts', expanded)}
|
||||
collapseKey='parts'
|
||||
>
|
||||
<ObjectTable
|
||||
type='part'
|
||||
visibleColumns={{
|
||||
product: false,
|
||||
'product._id': false
|
||||
}}
|
||||
masterFilter={{ 'product._id': productId }}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
|
||||
@ -8,7 +8,6 @@ import useCollapseState from '../../hooks/useCollapseState.js'
|
||||
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
||||
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import ViewButton from '../../common/ViewButton.jsx'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
@ -25,8 +24,6 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
|
||||
import FilePreview from '../../common/FilePreview.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||
import PartIcon from '../../../Icons/PartIcon.jsx'
|
||||
|
||||
const log = loglevel.getLogger('GCodeFileInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -40,8 +37,7 @@ const GCodeFileInfo = () => {
|
||||
'GCodeFileInfo',
|
||||
{
|
||||
info: true,
|
||||
parts: true,
|
||||
preview: true,
|
||||
stocks: true,
|
||||
notes: true,
|
||||
auditLogs: true
|
||||
}
|
||||
@ -98,7 +94,6 @@ const GCodeFileInfo = () => {
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'GCode File Information' },
|
||||
{ key: 'parts', label: 'Parts' },
|
||||
{ key: 'preview', label: 'GCode File Preview' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
@ -169,25 +164,7 @@ const GCodeFileInfo = () => {
|
||||
isEditing={isEditing}
|
||||
type='gcodeFile'
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
parts: false
|
||||
}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Parts'
|
||||
icon={<PartIcon />}
|
||||
active={collapseState.parts}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('parts', expanded)
|
||||
}
|
||||
collapseKey='parts'
|
||||
>
|
||||
<ObjectProperty
|
||||
{...getModelProperty('gcodeFile', 'parts')}
|
||||
isEditing={isEditing}
|
||||
objectData={objectData}
|
||||
loading={loading}
|
||||
visibleProperties={{}}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
|
||||
@ -241,10 +241,7 @@ const ControlPrinter = () => {
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<AlertsDisplay
|
||||
alerts={objectFormState.objectData?.alerts}
|
||||
printerId={printerId}
|
||||
/>
|
||||
<AlertsDisplay alerts={objectFormState.objectData?.alerts} />
|
||||
</Space>
|
||||
</Space>
|
||||
<Space>
|
||||
@ -324,9 +321,7 @@ const ControlPrinter = () => {
|
||||
currentJob: false,
|
||||
'currentJob._id': false,
|
||||
currentSubJob: false,
|
||||
'currentSubJob._id': false,
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
'currentSubJob._id': false
|
||||
}}
|
||||
objectData={printerObjectData}
|
||||
type='printer'
|
||||
|
||||
@ -1,208 +0,0 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Space, Flex, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||
import ActionHandler from '../../common/ActionHandler'
|
||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import StockEventIcon from '../../../Icons/StockEventIcon.jsx'
|
||||
|
||||
const SubJobInfo = () => {
|
||||
const location = useLocation()
|
||||
const objectFormRef = useRef(null)
|
||||
const actionHandlerRef = useRef(null)
|
||||
const subJobId = new URLSearchParams(location.search).get('subJobId')
|
||||
const [collapseState, updateCollapseState] = useCollapseState('SubJobInfo', {
|
||||
info: true,
|
||||
notes: true,
|
||||
auditLogs: true
|
||||
})
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
lock: null,
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
|
||||
const actions = {
|
||||
reload: () => {
|
||||
objectFormRef?.current.handleFetchObject()
|
||||
return true
|
||||
},
|
||||
edit: () => {
|
||||
objectFormRef?.current.startEditing()
|
||||
return false
|
||||
},
|
||||
cancelEdit: () => {
|
||||
objectFormRef?.current.cancelEditing()
|
||||
return true
|
||||
},
|
||||
finishEdit: () => {
|
||||
objectFormRef?.current.handleUpdate()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
gap='large'
|
||||
vertical='true'
|
||||
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||
>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size='middle'>
|
||||
<Space size='small'>
|
||||
<ObjectActions
|
||||
type='subJob'
|
||||
id={subJobId}
|
||||
disabled={objectFormState.loading}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
<ViewButton
|
||||
disabled={objectFormState.loading}
|
||||
items={[
|
||||
{ key: 'info', label: 'Sub Job Information' },
|
||||
{ key: 'notes', label: 'Notes' },
|
||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||
]}
|
||||
visibleState={collapseState}
|
||||
updateVisibleState={updateCollapseState}
|
||||
/>
|
||||
<DocumentPrintButton
|
||||
type='subJob'
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={objectFormState.loading}
|
||||
/>
|
||||
</Space>
|
||||
<LockIndicator lock={objectFormState.lock} />
|
||||
</Space>
|
||||
<Space>
|
||||
<EditButtons
|
||||
isEditing={objectFormState.isEditing}
|
||||
handleUpdate={() => {
|
||||
actionHandlerRef.current.callAction('finishEdit')
|
||||
}}
|
||||
cancelEditing={() => {
|
||||
actionHandlerRef.current.callAction('cancelEdit')
|
||||
}}
|
||||
startEditing={() => {
|
||||
actionHandlerRef.current.callAction('edit')
|
||||
}}
|
||||
editLoading={objectFormState.editLoading}
|
||||
formValid={objectFormState.formValid}
|
||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||
loading={objectFormState.editLoading}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<ScrollBox>
|
||||
<Flex vertical gap={'large'}>
|
||||
<ActionHandler
|
||||
actions={actions}
|
||||
loading={objectFormState.loading}
|
||||
ref={actionHandlerRef}
|
||||
>
|
||||
<InfoCollapse
|
||||
title='Sub Job Information'
|
||||
icon={<InfoCircleIcon />}
|
||||
active={collapseState.info}
|
||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||
collapseKey='info'
|
||||
>
|
||||
<ObjectForm
|
||||
id={subJobId}
|
||||
type='subJob'
|
||||
style={{ height: '100%' }}
|
||||
ref={objectFormRef}
|
||||
onStateChange={(state) => {
|
||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||
}}
|
||||
>
|
||||
{({ loading, isEditing, objectData }) => (
|
||||
<ObjectInfo
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
type='subJob'
|
||||
objectData={objectData}
|
||||
/>
|
||||
)}
|
||||
</ObjectForm>
|
||||
</InfoCollapse>
|
||||
</ActionHandler>
|
||||
<InfoCollapse
|
||||
title='Sub Job Stock Events'
|
||||
icon={<StockEventIcon />}
|
||||
active={collapseState.events}
|
||||
onToggle={(expanded) => updateCollapseState('events', expanded)}
|
||||
collapseKey='events'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='stockEvent'
|
||||
masterFilter={{ 'owner._id': subJobId }}
|
||||
visibleColumns={{
|
||||
'owner._id': false,
|
||||
'owner.type': false,
|
||||
owner: false
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Notes'
|
||||
icon={<NoteIcon />}
|
||||
active={collapseState.notes}
|
||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||
collapseKey='notes'
|
||||
>
|
||||
<Card>
|
||||
<NotesPanel _id={subJobId} type='subJob' />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
<InfoCollapse
|
||||
title='Audit Logs'
|
||||
icon={<AuditLogIcon />}
|
||||
active={collapseState.auditLogs}
|
||||
onToggle={(expanded) =>
|
||||
updateCollapseState('auditLogs', expanded)
|
||||
}
|
||||
collapseKey='auditLogs'
|
||||
>
|
||||
{objectFormState.loading ? (
|
||||
<InfoCollapsePlaceholder />
|
||||
) : (
|
||||
<ObjectTable
|
||||
type='auditLog'
|
||||
masterFilter={{ 'parent._id': subJobId }}
|
||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||
/>
|
||||
)}
|
||||
</InfoCollapse>
|
||||
</Flex>
|
||||
</ScrollBox>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SubJobInfo
|
||||
@ -1,29 +1,15 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { createElement } from 'react'
|
||||
import { Flex, Alert, Button, Dropdown, Popover } from 'antd'
|
||||
import { Flex, Alert } from 'antd'
|
||||
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
import { CaretDownOutlined } from '@ant-design/icons'
|
||||
|
||||
import { useMediaQuery } from 'react-responsive'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
const AlertsDisplay = ({
|
||||
alerts = [],
|
||||
printerId,
|
||||
showDismiss = true,
|
||||
showActions = true
|
||||
}) => {
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
const AlertsDisplay = ({ alerts = [] }) => {
|
||||
const getAlertType = (type, priority) => {
|
||||
if (type === 'error' || priority === '9') return 'error'
|
||||
if (type === 'warning' || priority === '8') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
const printerModel = getModelByName('printer')
|
||||
const navigate = useNavigate()
|
||||
const getAlertIcon = (type, priority) => {
|
||||
if (type === 'error' || priority === '9') return <ExclamationOctagonIcon />
|
||||
if (type === 'warning' || priority === '8')
|
||||
@ -31,185 +17,34 @@ const AlertsDisplay = ({
|
||||
return <InfoCircleIcon />
|
||||
}
|
||||
|
||||
// Recursively filter the printer model actions by a set of allowed action keys
|
||||
const filterActionsByKeys = (actions, allowedKeys) => {
|
||||
if (!Array.isArray(actions)) return []
|
||||
|
||||
const filtered = actions
|
||||
.map((action) => {
|
||||
if (action.type === 'divider') {
|
||||
return { type: 'divider' }
|
||||
}
|
||||
|
||||
const actionKey = action.key || action.name
|
||||
let children = []
|
||||
|
||||
if (Array.isArray(action.children)) {
|
||||
children = filterActionsByKeys(action.children, allowedKeys)
|
||||
}
|
||||
|
||||
const isAllowed = actionKey && allowedKeys.has(actionKey)
|
||||
|
||||
if (!isAllowed && children.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...action,
|
||||
children
|
||||
}
|
||||
})
|
||||
.filter((action) => action !== null)
|
||||
|
||||
// Clean up dividers: remove leading/trailing and consecutive dividers
|
||||
const cleaned = []
|
||||
for (const action of filtered) {
|
||||
if (action.type === 'divider') {
|
||||
if (cleaned.length === 0) continue
|
||||
if (cleaned[cleaned.length - 1].type === 'divider') continue
|
||||
}
|
||||
cleaned.push(action)
|
||||
}
|
||||
if (cleaned[cleaned.length - 1]?.type === 'divider') {
|
||||
cleaned.pop()
|
||||
}
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
// Map filtered printer actions to AntD Dropdown menu items (including children)
|
||||
const mapActionsToMenuItems = (actions) => {
|
||||
if (!Array.isArray(actions)) return []
|
||||
|
||||
return actions.map((action) => {
|
||||
if (action.type === 'divider') {
|
||||
return { type: 'divider' }
|
||||
}
|
||||
|
||||
const item = {
|
||||
key: action.key || action.name,
|
||||
label: action.label,
|
||||
icon: action.icon ? createElement(action.icon) : undefined
|
||||
}
|
||||
|
||||
if (Array.isArray(action.children) && action.children.length > 0) {
|
||||
item.children = mapActionsToMenuItems(action.children)
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
if (alerts.length == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const alertElements = alerts.map((alert, index) => {
|
||||
const printerActions = printerModel?.actions || []
|
||||
|
||||
const alertActionKeys = Array.isArray(alert?.actions)
|
||||
? alert.actions
|
||||
.map((action) =>
|
||||
typeof action === 'string'
|
||||
? action
|
||||
: action?.key || action?.name || null
|
||||
)
|
||||
.filter((key) => key != null)
|
||||
: []
|
||||
|
||||
const allowedKeys = new Set(alertActionKeys)
|
||||
const filteredActions = filterActionsByKeys(printerActions, allowedKeys)
|
||||
|
||||
const findActionByKey = (actions, key) => {
|
||||
if (!Array.isArray(actions)) return null
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.type === 'divider') continue
|
||||
|
||||
const actionKey = action.key || action.name
|
||||
if (actionKey === key) {
|
||||
return action
|
||||
}
|
||||
|
||||
if (Array.isArray(action.children) && action.children.length > 0) {
|
||||
const found = findActionByKey(action.children, key)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const menu = {
|
||||
items: mapActionsToMenuItems(filteredActions),
|
||||
onClick: ({ key }) => {
|
||||
const action = findActionByKey(filteredActions, key)
|
||||
|
||||
if (action?.url) {
|
||||
navigate(action.url(printerId))
|
||||
} else {
|
||||
console.warn('No action found for key:', key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap='small'>
|
||||
{alerts.map((alert, index) => (
|
||||
<Alert
|
||||
key={`${alert.createdAt}-${index}-${alert._id}`}
|
||||
key={`${alert.createdAt}-${index}`}
|
||||
message={alert.message}
|
||||
style={{ padding: '4px 10px 4px 8px' }}
|
||||
type={getAlertType(alert.type, alert.priority)}
|
||||
icon={getAlertIcon(alert.type, alert.priority)}
|
||||
showIcon
|
||||
closable={showDismiss && alert.canDismiss}
|
||||
onClose={() => {
|
||||
console.log('Closing alert:', alert._id)
|
||||
}}
|
||||
action={
|
||||
showActions ? (
|
||||
<Dropdown menu={menu} on>
|
||||
<Button size='small' type='text' style={{ marginLeft: '5px' }}>
|
||||
<CaretDownOutlined />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
)
|
||||
})
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Popover
|
||||
content={alertElements}
|
||||
trigger='hover'
|
||||
arrow={false}
|
||||
placement='bottom'
|
||||
classNames={{
|
||||
root: 'printer-alerts-display-popover'
|
||||
}}
|
||||
>
|
||||
<Button>Alerts</Button>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
return <Flex gap='small'>{alertElements}</Flex>
|
||||
}
|
||||
|
||||
AlertsDisplay.propTypes = {
|
||||
printerId: PropTypes.string.isRequired,
|
||||
showActions: PropTypes.bool.isRequired,
|
||||
showDismiss: PropTypes.bool.isRequired,
|
||||
alerts: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
canDismiss: PropTypes.bool.isRequired,
|
||||
_id: PropTypes.string.isRequired,
|
||||
priority: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
updatedAt: PropTypes.string.isRequired,
|
||||
message: PropTypes.string,
|
||||
actions: PropTypes.arrayOf(PropTypes.string)
|
||||
message: PropTypes.string.isRequired
|
||||
})
|
||||
).isRequired
|
||||
}
|
||||
|
||||
@ -153,7 +153,7 @@ const DashboardNavigation = () => {
|
||||
fontSize: '46px',
|
||||
height: '16px',
|
||||
marginLeft: '15px',
|
||||
marginRight: '8px'
|
||||
marginRight: '5px'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -313,7 +313,7 @@ const DashboardNavigation = () => {
|
||||
{isElectron ? (
|
||||
<Flex
|
||||
className='ant-menu-horizontal ant-menu-light electron-navigation-wrapper'
|
||||
style={{ lineHeight: '40px', padding: '0 2px 0 2px' }}
|
||||
style={{ lineHeight: '40px', padding: '0 4px 0 4px' }}
|
||||
>
|
||||
{navigationContents}
|
||||
</Flex>
|
||||
|
||||
@ -36,7 +36,7 @@ const DashboardWindowButtons = () => {
|
||||
<Flex align='center'>
|
||||
{platform == 'darwin' ? (
|
||||
isFullScreen == false ? (
|
||||
<div style={{ width: '80px' }} />
|
||||
<div style={{ width: '65px' }} />
|
||||
) : null
|
||||
) : (
|
||||
<div style={{ width: '95px' }}>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Upload, Button, Flex, Typography, Space, Progress, Card } from 'antd'
|
||||
import { Upload, Button, Flex, Typography, Space } from 'antd'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ApiServerContext } from '../context/ApiServerContext'
|
||||
import UploadIcon from '../../Icons/UploadIcon'
|
||||
@ -6,7 +6,6 @@ import { useContext, useState, useEffect } from 'react'
|
||||
import ObjectSelect from './ObjectSelect'
|
||||
import FileList from './FileList'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
@ -19,8 +18,6 @@ const FileUpload = ({
|
||||
showInfo
|
||||
}) => {
|
||||
const { uploadFile } = useContext(ApiServerContext)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [uploadProgress, setUploadProgress] = useState(0)
|
||||
|
||||
// Track current files using useState
|
||||
const [currentFiles, setCurrentFiles] = useState(() => {
|
||||
@ -59,11 +56,7 @@ const FileUpload = ({
|
||||
|
||||
const handleFileUpload = async (file) => {
|
||||
try {
|
||||
setUploading(true)
|
||||
const uploadedFile = await uploadFile(file, {}, (progress) => {
|
||||
setUploadProgress(progress)
|
||||
})
|
||||
setUploading(false)
|
||||
const uploadedFile = await uploadFile(file)
|
||||
if (uploadedFile) {
|
||||
if (multiple) {
|
||||
// For multiple files, add to existing array
|
||||
@ -102,7 +95,7 @@ const FileUpload = ({
|
||||
|
||||
return (
|
||||
<Flex gap={'small'} vertical>
|
||||
{hasNoItems && uploading == false ? (
|
||||
{hasNoItems ? (
|
||||
<Flex gap={'small'} align='center'>
|
||||
<Space.Compact style={{ flexGrow: 1 }}>
|
||||
<ObjectSelect
|
||||
@ -130,29 +123,6 @@ const FileUpload = ({
|
||||
</Upload>
|
||||
</Flex>
|
||||
) : null}
|
||||
{uploading == true ? (
|
||||
<Card styles={{ body: { padding: '10px 15px' } }}>
|
||||
<Flex gap={'small'} align='center'>
|
||||
<Text>Uploading...</Text>
|
||||
{uploadProgress > 0 ? (
|
||||
<>
|
||||
{uploadProgress >= 0 && uploadProgress < 100 ? (
|
||||
<>
|
||||
<Progress
|
||||
percent={uploadProgress}
|
||||
showInfo={false}
|
||||
style={{ width: '100px', flexGrow: 1 }}
|
||||
status='active'
|
||||
/>
|
||||
<Text>{uploadProgress}%</Text>
|
||||
</>
|
||||
) : null}
|
||||
{uploadProgress == 100 ? <LoadingOutlined /> : null}
|
||||
</>
|
||||
) : null}
|
||||
</Flex>
|
||||
</Card>
|
||||
) : null}
|
||||
<FileList
|
||||
files={currentFiles}
|
||||
multiple={multiple}
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { Flex, Typography } from 'antd'
|
||||
import CopyButton from './CopyButton'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const MiscId = ({ value, showCopy = true }) => {
|
||||
if (!value) {
|
||||
return <Text type='secondary'>n/a</Text>
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align={'end'}
|
||||
className='miscid'
|
||||
style={{ minWidth: '0px', width: '100%' }}
|
||||
>
|
||||
<Text
|
||||
code
|
||||
ellipsis
|
||||
style={showCopy ? { marginRight: 6, minWidth: '0px' } : undefined}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
|
||||
{showCopy && (
|
||||
<CopyButton
|
||||
text={value}
|
||||
tooltip='Copy ID'
|
||||
style={{ marginLeft: 0 }}
|
||||
iconStyle={{ fontSize: '14px' }}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
MiscId.propTypes = {
|
||||
value: PropTypes.string,
|
||||
showCopy: PropTypes.bool
|
||||
}
|
||||
|
||||
export default MiscId
|
||||
@ -102,9 +102,9 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||
form={form}
|
||||
layout='vertical'
|
||||
style={style}
|
||||
onValuesChange={(_changedValues, allFormValues) => {
|
||||
onValuesChange={(values) => {
|
||||
// Calculate computed values based on current form data
|
||||
const currentFormData = { ...objectData, ...allFormValues }
|
||||
const currentFormData = { ...objectData, ...values }
|
||||
const computedValues = calculateComputedValues(currentFormData, model)
|
||||
|
||||
// Update form with computed values if any were calculated
|
||||
@ -113,7 +113,7 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||
}
|
||||
|
||||
// Merge all values (user input + computed values)
|
||||
const allValues = { ...allFormValues, ...computedValues }
|
||||
const allValues = { ...values, ...computedValues }
|
||||
setObjectData((prev) => {
|
||||
return merge({}, prev, allValues)
|
||||
})
|
||||
|
||||
@ -3,8 +3,6 @@ import { Dropdown, Button } from 'antd'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { useActionsModal } from '../context/ActionsModalContext'
|
||||
import KeyboardShortcut from './KeyboardShortcut'
|
||||
|
||||
// Recursively filter actions based on visibleActions
|
||||
function filterActionsByVisibility(actions, visibleActions) {
|
||||
@ -45,7 +43,6 @@ function mapActionsToMenuItems(actions, currentUrlWithActions, id, objectData) {
|
||||
const actionUrl = action.url ? action.url(id) : undefined
|
||||
|
||||
var disabled = actionUrl && actionUrl === currentUrlWithActions
|
||||
var visible = true
|
||||
|
||||
if (action.disabled) {
|
||||
if (typeof action.disabled === 'function') {
|
||||
@ -55,14 +52,6 @@ function mapActionsToMenuItems(actions, currentUrlWithActions, id, objectData) {
|
||||
}
|
||||
}
|
||||
|
||||
if (action.visible) {
|
||||
if (typeof action.visible === 'function') {
|
||||
visible = action.visible(objectData)
|
||||
} else {
|
||||
visible = action.visible
|
||||
}
|
||||
}
|
||||
|
||||
const item = {
|
||||
key: action.key || action.name,
|
||||
label: action.label,
|
||||
@ -78,9 +67,7 @@ function mapActionsToMenuItems(actions, currentUrlWithActions, id, objectData) {
|
||||
objectData
|
||||
)
|
||||
}
|
||||
if (visible == true) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -104,7 +91,6 @@ const ObjectActions = ({
|
||||
const actions = model.actions || []
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { showActionsModal } = useActionsModal()
|
||||
|
||||
// Get current url without 'action' param
|
||||
const currentUrlWithoutActions = stripActionParam(
|
||||
@ -154,20 +140,11 @@ const ObjectActions = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardShortcut
|
||||
shortcut='alt+a'
|
||||
onTrigger={() => showActionsModal(id, type, objectData)}
|
||||
>
|
||||
<Dropdown menu={menu} {...dropdownProps}>
|
||||
<Button
|
||||
{...buttonProps}
|
||||
disabled={disabled}
|
||||
onClick={() => showActionsModal(id, type, objectData)}
|
||||
>
|
||||
<Button {...buttonProps} disabled={disabled}>
|
||||
Actions
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</KeyboardShortcut>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,469 +0,0 @@
|
||||
import { useMemo, useEffect, useRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Table, Skeleton, Card, Button, Flex, Form, Typography } from 'antd'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ObjectProperty from './ObjectProperty'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
const { Text } = Typography
|
||||
|
||||
const DEFAULT_COLUMN_WIDTHS = {
|
||||
text: 200,
|
||||
number: 120,
|
||||
dateTime: 200,
|
||||
state: 200,
|
||||
id: 180,
|
||||
bool: 120,
|
||||
tags: 200
|
||||
}
|
||||
|
||||
const getDefaultWidth = (type) => {
|
||||
return DEFAULT_COLUMN_WIDTHS[type] || 200
|
||||
}
|
||||
|
||||
const createSkeletonRows = (rowCount, keyPrefix, keyName) => {
|
||||
return Array.from({ length: rowCount }).map((_, index) => {
|
||||
const skeletonKey = `${keyPrefix}-${index}`
|
||||
const row = {
|
||||
isSkeleton: true,
|
||||
_objectChildTableKey: skeletonKey
|
||||
}
|
||||
|
||||
if (typeof keyName === 'string') {
|
||||
row[keyName] = skeletonKey
|
||||
}
|
||||
|
||||
return row
|
||||
})
|
||||
}
|
||||
|
||||
const ObjectChildTable = ({
|
||||
maxWidth = '100%',
|
||||
properties = [],
|
||||
columns = [],
|
||||
visibleColumns = {},
|
||||
objectData = null,
|
||||
scrollHeight = 240,
|
||||
size = 'small',
|
||||
loading = false,
|
||||
rowKey = '_id',
|
||||
skeletonRows = 5,
|
||||
additionalColumns = [],
|
||||
emptyText = 'No items',
|
||||
isEditing = false,
|
||||
formListName,
|
||||
value = [],
|
||||
rollups = [],
|
||||
onChange,
|
||||
...tableProps
|
||||
}) => {
|
||||
const mainTableWrapperRef = useRef(null)
|
||||
const rollupTableWrapperRef = useRef(null)
|
||||
|
||||
const propertyMap = useMemo(() => {
|
||||
const map = new Map()
|
||||
properties.forEach((property) => {
|
||||
if (property?.name) {
|
||||
map.set(property.name, property)
|
||||
}
|
||||
})
|
||||
return map
|
||||
}, [properties])
|
||||
|
||||
const orderedPropertyNames = useMemo(() => {
|
||||
if (columns && columns.length > 0) {
|
||||
return columns
|
||||
}
|
||||
return properties.map((property) => property.name).filter(Boolean)
|
||||
}, [columns, properties])
|
||||
|
||||
const resolvedProperties = useMemo(() => {
|
||||
const explicit = orderedPropertyNames
|
||||
.map((name) => propertyMap.get(name))
|
||||
.filter(Boolean)
|
||||
|
||||
const remaining = properties.filter(
|
||||
(property) => !orderedPropertyNames.includes(property.name)
|
||||
)
|
||||
|
||||
return [...explicit, ...remaining].filter((property) => {
|
||||
if (!property?.name) return false
|
||||
if (
|
||||
visibleColumns &&
|
||||
Object.prototype.hasOwnProperty.call(visibleColumns, property.name)
|
||||
) {
|
||||
return visibleColumns[property.name] !== false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}, [orderedPropertyNames, propertyMap, properties, visibleColumns])
|
||||
|
||||
// When used inside antd Form.Item without Form.List, `value` will be the controlled array.
|
||||
const itemsSource = useMemo(() => {
|
||||
return value ?? []
|
||||
}, [value])
|
||||
|
||||
// When used with antd Form.List, grab the form instance so we can read
|
||||
// the latest row values and pass them into ObjectProperty as objectData.
|
||||
// Assumes this component is rendered within a Form context when editing.
|
||||
const formInstance = Form.useFormInstance()
|
||||
|
||||
const listNamePath = useMemo(() => {
|
||||
if (!formListName) return null
|
||||
return Array.isArray(formListName) ? formListName : [formListName]
|
||||
}, [formListName])
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
const propertyColumns = resolvedProperties.map((property) => ({
|
||||
title: property.label || property.name,
|
||||
dataIndex: property.name,
|
||||
key: property.name,
|
||||
width: property.columnWidth || getDefaultWidth(property.type),
|
||||
render: (_text, record) => {
|
||||
if (record?.isSkeleton) {
|
||||
return (
|
||||
<Skeleton.Input active size='small' style={{ width: '100%' }} />
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ObjectProperty
|
||||
{...property}
|
||||
longId={false}
|
||||
objectData={record}
|
||||
isEditing={isEditing}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}))
|
||||
|
||||
return [...propertyColumns, ...additionalColumns]
|
||||
}, [resolvedProperties, additionalColumns, isEditing])
|
||||
|
||||
const skeletonData = useMemo(() => {
|
||||
return createSkeletonRows(
|
||||
skeletonRows,
|
||||
'object-child-table-skeleton',
|
||||
typeof rowKey === 'string' ? rowKey : null
|
||||
)
|
||||
}, [skeletonRows, rowKey])
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
if (loading && (!itemsSource || itemsSource.length === 0)) {
|
||||
return skeletonData
|
||||
}
|
||||
return itemsSource
|
||||
}, [itemsSource, loading, skeletonData])
|
||||
|
||||
const resolvedRowKey =
|
||||
typeof rowKey === 'function' ? rowKey : (_record, index) => index
|
||||
|
||||
const scrollConfig =
|
||||
scrollHeight != null
|
||||
? { y: scrollHeight, x: 'max-content' }
|
||||
: { x: 'max-content' }
|
||||
|
||||
const handleAddItem = () => {
|
||||
const newItem = {}
|
||||
|
||||
resolvedProperties.forEach((property) => {
|
||||
if (
|
||||
property?.name &&
|
||||
!Object.prototype.hasOwnProperty.call(newItem, property.name)
|
||||
) {
|
||||
newItem[property.name] = null
|
||||
}
|
||||
})
|
||||
|
||||
const currentItems = Array.isArray(itemsSource) ? itemsSource : []
|
||||
const newItems = [...currentItems, newItem]
|
||||
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(newItems)
|
||||
}
|
||||
}
|
||||
|
||||
const rollupDataSource = useMemo(() => {
|
||||
if (!rollups || rollups.length === 0) return []
|
||||
|
||||
// Single summary row where each rollup value is placed under
|
||||
// the column that matches its `property` field.
|
||||
const summaryRow = {}
|
||||
|
||||
properties.forEach((property) => {
|
||||
const rollup = rollups.find(
|
||||
(r) => r.property && r.property === property.name
|
||||
)
|
||||
|
||||
if (rollup && typeof rollup.value === 'function') {
|
||||
try {
|
||||
const updatedObjectData = { ...objectData }
|
||||
console.log('UPDATED OBJECT DATA', value)
|
||||
updatedObjectData[property.name] = value
|
||||
summaryRow[property.name] = rollup.value(updatedObjectData)
|
||||
} catch (e) {
|
||||
// Fail quietly but log for debugging
|
||||
console.error('Error computing rollup', rollup.name, e)
|
||||
summaryRow[property.name] = null
|
||||
}
|
||||
} else {
|
||||
summaryRow[property.name] = null
|
||||
}
|
||||
})
|
||||
|
||||
return [summaryRow]
|
||||
}, [properties, rollups, objectData])
|
||||
|
||||
const rollupColumns = useMemo(() => {
|
||||
return properties.map((property, index) => {
|
||||
const nextProperty = properties[index + 1]
|
||||
var nextRollup = null
|
||||
if (nextProperty) {
|
||||
nextRollup = rollups?.find(
|
||||
(r) => r.property && r.property === nextProperty.name
|
||||
)
|
||||
}
|
||||
const rollupLabel = nextRollup?.label
|
||||
return {
|
||||
title: <Text>{property.label || property.name}</Text>,
|
||||
dataIndex: property.name,
|
||||
key: property.name,
|
||||
width: property.columnWidth || getDefaultWidth(property.type),
|
||||
render: (_text, record) => {
|
||||
return (
|
||||
<Flex justify={'space-between'}>
|
||||
<Text>{record[property.name]}</Text>
|
||||
{rollupLabel && <Text type='secondary'>{rollupLabel}:</Text>}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [properties, rollups])
|
||||
|
||||
const hasRollups = useMemo(
|
||||
() => Array.isArray(rollups) && rollups.length > 0,
|
||||
[rollups]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasRollups || isEditing == null) return
|
||||
|
||||
const mainWrapper = mainTableWrapperRef.current
|
||||
const rollupWrapper = rollupTableWrapperRef.current
|
||||
|
||||
if (!mainWrapper || !rollupWrapper) return
|
||||
|
||||
const mainBody =
|
||||
mainWrapper.querySelector('.ant-table-body') ||
|
||||
mainWrapper.querySelector('.ant-table-content')
|
||||
const rollupBody =
|
||||
rollupWrapper.querySelector('.ant-table-body') ||
|
||||
rollupWrapper.querySelector('.ant-table-content')
|
||||
|
||||
if (!mainBody || !rollupBody) return
|
||||
|
||||
let isSyncing = false
|
||||
|
||||
const syncScroll = (source, target) => {
|
||||
if (!target) return
|
||||
isSyncing = true
|
||||
target.scrollLeft = source.scrollLeft
|
||||
window.requestAnimationFrame(() => {
|
||||
isSyncing = false
|
||||
})
|
||||
}
|
||||
|
||||
const handleMainScroll = () => {
|
||||
if (isSyncing) return
|
||||
syncScroll(mainBody, rollupBody)
|
||||
}
|
||||
|
||||
const handleRollupScroll = () => {
|
||||
if (isSyncing) return
|
||||
syncScroll(rollupBody, mainBody)
|
||||
}
|
||||
|
||||
mainBody.addEventListener('scroll', handleMainScroll)
|
||||
rollupBody.addEventListener('scroll', handleRollupScroll)
|
||||
|
||||
return () => {
|
||||
mainBody.removeEventListener('scroll', handleMainScroll)
|
||||
rollupBody.removeEventListener('scroll', handleRollupScroll)
|
||||
}
|
||||
}, [hasRollups, isEditing])
|
||||
|
||||
const rollupTable = hasRollups ? (
|
||||
<div ref={rollupTableWrapperRef}>
|
||||
<Table
|
||||
dataSource={rollupDataSource}
|
||||
showHeader={false}
|
||||
columns={rollupColumns}
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
size={size}
|
||||
rowKey={resolvedRowKey}
|
||||
scroll={scrollConfig}
|
||||
locale={{ emptyText }}
|
||||
style={{ maxWidth }}
|
||||
className='rollup-table'
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
|
||||
const tableComponent = (
|
||||
<Flex vertical>
|
||||
<div ref={mainTableWrapperRef}>
|
||||
<Table
|
||||
style={{ maxWidth }}
|
||||
dataSource={dataSource}
|
||||
columns={tableColumns}
|
||||
loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
|
||||
pagination={false}
|
||||
size={size}
|
||||
rowKey={resolvedRowKey}
|
||||
scroll={scrollConfig}
|
||||
locale={{ emptyText }}
|
||||
className={hasRollups ? 'child-table-rollups' : 'child-table'}
|
||||
{...tableProps}
|
||||
/>
|
||||
</div>
|
||||
{rollupTable}
|
||||
</Flex>
|
||||
)
|
||||
|
||||
// When editing and a Form.List name is provided, bind rows via Form.List
|
||||
// instead of the manual value/onChange mechanism.
|
||||
if (isEditing === true && formListName) {
|
||||
return (
|
||||
<Form.List name={formListName}>
|
||||
{(fields, { add }) => {
|
||||
const listDataSource = fields.map((field, index) => ({
|
||||
_field: field,
|
||||
_index: index,
|
||||
key: field.key
|
||||
}))
|
||||
const listColumns = resolvedProperties.map((property) => ({
|
||||
title: property.label || property.name,
|
||||
dataIndex: property.name,
|
||||
key: property.name,
|
||||
width: property.columnWidth || getDefaultWidth(property.type),
|
||||
render: (_text, record) => {
|
||||
const field = record?._field
|
||||
if (!field) return null
|
||||
|
||||
// Resolve the most up-to-date row data for this index from the form
|
||||
let rowObjectData = undefined
|
||||
if (formInstance && listNamePath) {
|
||||
const namePath = [...listNamePath, field.name]
|
||||
rowObjectData = formInstance.getFieldValue(namePath)
|
||||
}
|
||||
return (
|
||||
<ObjectProperty
|
||||
{...property}
|
||||
// Bind directly to this list item + property via NamePath
|
||||
name={[field.name, property.name]}
|
||||
longId={false}
|
||||
isEditing={true}
|
||||
objectData={rowObjectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}))
|
||||
|
||||
const listTable = (
|
||||
<Flex vertical>
|
||||
<div ref={mainTableWrapperRef}>
|
||||
<Table
|
||||
dataSource={listDataSource}
|
||||
columns={[...listColumns, ...additionalColumns]}
|
||||
pagination={false}
|
||||
size={size}
|
||||
loading={loading}
|
||||
rowKey={(record) => record.key ?? record._index}
|
||||
scroll={scrollConfig}
|
||||
locale={{ emptyText }}
|
||||
className={hasRollups ? 'child-table-rollups' : 'child-table'}
|
||||
{...tableProps}
|
||||
/>
|
||||
</div>
|
||||
{rollupTable}
|
||||
</Flex>
|
||||
)
|
||||
|
||||
const handleAddListItem = () => {
|
||||
const newItem = {}
|
||||
resolvedProperties.forEach((property) => {
|
||||
if (property?.name) {
|
||||
newItem[property.name] = null
|
||||
}
|
||||
})
|
||||
add(newItem)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Flex vertical gap={'middle'}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Button>Actions</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<PlusIcon />}
|
||||
onClick={handleAddListItem}
|
||||
/>
|
||||
</Flex>
|
||||
{listTable}
|
||||
</Flex>
|
||||
</Card>
|
||||
)
|
||||
}}
|
||||
</Form.List>
|
||||
)
|
||||
}
|
||||
|
||||
if (isEditing === true) {
|
||||
return (
|
||||
<Card>
|
||||
<Flex vertical gap={'middle'}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Button>Actions</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<PlusIcon />}
|
||||
onClick={handleAddItem}
|
||||
/>
|
||||
</Flex>
|
||||
{tableComponent}
|
||||
</Flex>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return tableComponent
|
||||
}
|
||||
|
||||
ObjectChildTable.propTypes = {
|
||||
properties: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
type: PropTypes.string
|
||||
})
|
||||
).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.string),
|
||||
visibleColumns: PropTypes.object,
|
||||
scrollHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
size: PropTypes.string,
|
||||
loading: PropTypes.bool,
|
||||
rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
skeletonRows: PropTypes.number,
|
||||
additionalColumns: PropTypes.arrayOf(PropTypes.object),
|
||||
emptyText: PropTypes.node,
|
||||
isEditing: PropTypes.bool,
|
||||
formListName: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
value: PropTypes.arrayOf(PropTypes.object),
|
||||
onChange: PropTypes.func,
|
||||
maxWidth: PropTypes.string,
|
||||
rollups: PropTypes.arrayOf(PropTypes.object),
|
||||
objectData: PropTypes.object
|
||||
}
|
||||
|
||||
export default ObjectChildTable
|
||||
@ -37,7 +37,6 @@ const ObjectForm = forwardRef(
|
||||
const [lock, setLock] = useState({})
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const isEditingRef = useRef(false)
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
|
||||
const [form] = Form.useForm()
|
||||
@ -147,8 +146,7 @@ const ObjectForm = forwardRef(
|
||||
const currentFormValues = form.getFieldsValue()
|
||||
const mergedObjectData = {
|
||||
...serverObjectData.current,
|
||||
...currentFormValues,
|
||||
_isEditing: isEditingRef.current
|
||||
...currentFormValues
|
||||
}
|
||||
|
||||
form
|
||||
@ -200,7 +198,7 @@ const ObjectForm = forwardRef(
|
||||
const lockEvent = await fetchObjectLock(id, type)
|
||||
setLock(lockEvent)
|
||||
onStateChangeRef.current({ lock: lockEvent })
|
||||
setObjectData({ ...data, _isEditing: isEditingRef.current })
|
||||
setObjectData(data)
|
||||
serverObjectData.current = data
|
||||
|
||||
// Calculate and set computed values on initial load
|
||||
@ -277,13 +275,7 @@ const ObjectForm = forwardRef(
|
||||
|
||||
const startEditing = () => {
|
||||
setIsEditing(true)
|
||||
isEditingRef.current = true
|
||||
console.log('IS EDITING TRUE')
|
||||
setObjectData((prev) => ({ ...prev, _isEditing: isEditingRef.current }))
|
||||
onStateChangeRef.current({
|
||||
isEditing: true,
|
||||
objectData: { ...objectData, _isEditing: isEditingRef.current }
|
||||
})
|
||||
onStateChangeRef.current({ isEditing: true })
|
||||
lockObject(id, type)
|
||||
}
|
||||
|
||||
@ -295,14 +287,12 @@ const ObjectForm = forwardRef(
|
||||
model
|
||||
)
|
||||
const resetFormData = { ...serverObjectData.current, ...computedValues }
|
||||
setIsEditing(false)
|
||||
isEditingRef.current = false
|
||||
form.setFieldsValue(resetFormData)
|
||||
console.log('IS EDITING FALSE')
|
||||
setObjectData({ ...resetFormData, _isEditing: isEditingRef.current })
|
||||
}
|
||||
|
||||
onStateChangeRef.current({ isEditing: isEditingRef.current })
|
||||
form.setFieldsValue(resetFormData)
|
||||
setObjectData(resetFormData)
|
||||
}
|
||||
setIsEditing(false)
|
||||
onStateChangeRef.current({ isEditing: false })
|
||||
unlockObject(id, type)
|
||||
}
|
||||
|
||||
@ -312,15 +302,9 @@ const ObjectForm = forwardRef(
|
||||
setEditLoading(true)
|
||||
onStateChangeRef.current({ editLoading: true })
|
||||
await updateObject(id, type, value)
|
||||
|
||||
setObjectData({ ...objectData, ...value })
|
||||
setIsEditing(false)
|
||||
isEditingRef.current = false
|
||||
onStateChangeRef.current({ isEditing: isEditingRef.current })
|
||||
setObjectData({
|
||||
...objectData,
|
||||
...value,
|
||||
_isEditing: isEditingRef.current
|
||||
})
|
||||
onStateChangeRef.current({ isEditing: false })
|
||||
messageApi.success('Information updated successfully')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
@ -390,19 +374,13 @@ const ObjectForm = forwardRef(
|
||||
form={form}
|
||||
layout='vertical'
|
||||
style={style}
|
||||
onValuesChange={(changedValues, allFormValues) => {
|
||||
// Use the full form snapshot (allFormValues) so list fields (Form.List)
|
||||
// come through as complete arrays instead of sparse arrays like
|
||||
// [null, null, { quantity: 5 }].
|
||||
onValuesChange={(values) => {
|
||||
if (onEdit != undefined) {
|
||||
onEdit(allFormValues)
|
||||
onEdit(values)
|
||||
}
|
||||
|
||||
// Calculate computed values based on current form data
|
||||
const currentFormData = {
|
||||
...(serverObjectData.current || {}),
|
||||
...allFormValues
|
||||
}
|
||||
const currentFormData = { ...objectData, ...values }
|
||||
const computedValues = calculateComputedValues(
|
||||
currentFormData,
|
||||
model
|
||||
@ -422,12 +400,8 @@ const ObjectForm = forwardRef(
|
||||
}
|
||||
}
|
||||
|
||||
// Merge all values (user input + computed values) and keep editing flag
|
||||
const allValues = {
|
||||
...allFormValues,
|
||||
...computedValues,
|
||||
_isEditing: isEditingRef.current
|
||||
}
|
||||
// Merge all values (user input + computed values)
|
||||
const allValues = { ...values, ...computedValues }
|
||||
|
||||
setObjectData((prev) => {
|
||||
return { ...prev, ...allValues }
|
||||
|
||||
@ -43,8 +43,6 @@ import AlertsDisplay from './AlertsDisplay'
|
||||
import FileUpload from './FileUpload'
|
||||
import DataTree from './DataTree'
|
||||
import FileList from './FileList'
|
||||
import ObjectChildTable from './ObjectChildTable'
|
||||
import MiscId from './MiscId'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
@ -88,11 +86,6 @@ const ObjectProperty = ({
|
||||
roundNumber = false,
|
||||
showHyperlink,
|
||||
showSince,
|
||||
properties = [],
|
||||
onChange = null,
|
||||
maxWidth = '100%',
|
||||
loading = false,
|
||||
rollups = [],
|
||||
...rest
|
||||
}) => {
|
||||
if (value && typeof value == 'function' && objectData) {
|
||||
@ -386,18 +379,6 @@ const ObjectProperty = ({
|
||||
case 'objectList': {
|
||||
return <ObjectList value={value} objectType={objectType} />
|
||||
}
|
||||
case 'objectChildren': {
|
||||
return (
|
||||
<ObjectChildTable
|
||||
value={value}
|
||||
properties={properties}
|
||||
objectData={objectData}
|
||||
maxWidth={maxWidth}
|
||||
loading={loading}
|
||||
rollups={rollups}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'state': {
|
||||
if (value && value?.type) {
|
||||
return <StateDisplay {...rest} state={value} />
|
||||
@ -438,9 +419,6 @@ const ObjectProperty = ({
|
||||
)
|
||||
}
|
||||
}
|
||||
case 'miscId': {
|
||||
return <MiscId value={value} {...rest} />
|
||||
}
|
||||
case 'density': {
|
||||
if (value != null) {
|
||||
return <Text {...textParams}>{`${value} g/cm³`}</Text>
|
||||
@ -454,14 +432,7 @@ const ObjectProperty = ({
|
||||
}
|
||||
case 'alerts': {
|
||||
if (value != null && value?.length != 0) {
|
||||
return (
|
||||
<AlertsDisplay
|
||||
alerts={value}
|
||||
printerId={objectData._id}
|
||||
showDismiss={false}
|
||||
showActions={false}
|
||||
/>
|
||||
)
|
||||
return <AlertsDisplay alerts={value} />
|
||||
} else {
|
||||
return (
|
||||
<Text type='secondary' {...textParams}>
|
||||
@ -575,11 +546,6 @@ const ObjectProperty = ({
|
||||
margin: 0,
|
||||
...(mergedFormItemProps.style || {})
|
||||
}
|
||||
|
||||
if (typeof onChange === 'function') {
|
||||
mergedFormItemProps.onChange = onChange
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'netGross':
|
||||
return (
|
||||
@ -770,7 +736,7 @@ const ObjectProperty = ({
|
||||
case 'objectType':
|
||||
return (
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<ObjectTypeSelect disabled={disabled} masterFilter={masterFilter} />
|
||||
<ObjectTypeSelect disabled={disabled} />
|
||||
</Form.Item>
|
||||
)
|
||||
case 'objectList':
|
||||
@ -809,18 +775,6 @@ const ObjectProperty = ({
|
||||
/>
|
||||
</Form.Item>
|
||||
)
|
||||
case 'objectChildren': {
|
||||
return (
|
||||
<ObjectChildTable
|
||||
value={value}
|
||||
properties={properties}
|
||||
objectData={objectData}
|
||||
isEditing={true}
|
||||
formListName={formItemName}
|
||||
rollups={rollups}
|
||||
/>
|
||||
)
|
||||
}
|
||||
default:
|
||||
return (
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
@ -861,9 +815,7 @@ ObjectProperty.propTypes = {
|
||||
showPreview: PropTypes.bool,
|
||||
showHyperlink: PropTypes.bool,
|
||||
options: PropTypes.array,
|
||||
showSince: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
rollups: PropTypes.arrayOf(PropTypes.object)
|
||||
showSince: PropTypes.bool
|
||||
}
|
||||
|
||||
export default ObjectProperty
|
||||
|
||||
@ -10,18 +10,11 @@ const ObjectTypeSelect = ({
|
||||
placeholder = 'Select object type...',
|
||||
showSearch = true,
|
||||
allowClear = true,
|
||||
disabled = false,
|
||||
masterFilter = null
|
||||
disabled = false
|
||||
}) => {
|
||||
// Create options from object models
|
||||
const options = objectModels
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.filter((model) => {
|
||||
if (masterFilter == null) {
|
||||
return true
|
||||
}
|
||||
return masterFilter.includes(model?.name)
|
||||
})
|
||||
.map((model) => ({
|
||||
value: model.name,
|
||||
label: <ObjectTypeDisplay objectType={model.name} />,
|
||||
@ -53,8 +46,7 @@ ObjectTypeSelect.propTypes = {
|
||||
placeholder: PropTypes.string,
|
||||
showSearch: PropTypes.bool,
|
||||
allowClear: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
masterFilter: PropTypes.object
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
export default ObjectTypeSelect
|
||||
|
||||
@ -70,7 +70,7 @@ const PrinterTemperaturePanel = ({
|
||||
}, [temperatureData.bed?.target])
|
||||
|
||||
useEffect(() => {
|
||||
if (id && connected == true) {
|
||||
if (id && connected) {
|
||||
const temperatureEventUnsubscribe = subscribeToObjectEvent(
|
||||
id,
|
||||
'printer',
|
||||
|
||||
@ -58,7 +58,6 @@ const PropertyChanges = ({ type, value }) => {
|
||||
longId={false}
|
||||
minimal={true}
|
||||
objectData={value?.old}
|
||||
maxWidth='200px'
|
||||
/>
|
||||
) : null}
|
||||
{value?.old && value?.new ? (
|
||||
@ -72,7 +71,6 @@ const PropertyChanges = ({ type, value }) => {
|
||||
longId={false}
|
||||
minimal={true}
|
||||
objectData={value?.new}
|
||||
maxWidth='200px'
|
||||
/>
|
||||
) : null}
|
||||
</Flex>
|
||||
|
||||
@ -18,12 +18,8 @@ const StateDisplay = ({
|
||||
'processing',
|
||||
'queued',
|
||||
'printing',
|
||||
'used',
|
||||
'deploying'
|
||||
'used'
|
||||
]
|
||||
const orangeProgressTypes = ['used', 'deploying', 'queued']
|
||||
const activeProgressTypes = ['printing', 'deploying']
|
||||
|
||||
const currentState = state || {
|
||||
type: 'unknown',
|
||||
progress: 0
|
||||
@ -43,12 +39,8 @@ const StateDisplay = ({
|
||||
currentState?.progress > 0 ? (
|
||||
<Progress
|
||||
percent={Math.round(currentState.progress * 100)}
|
||||
status={
|
||||
activeProgressTypes.includes(currentState.type) ? 'active' : ''
|
||||
}
|
||||
strokeColor={
|
||||
orangeProgressTypes.includes(currentState.type) ? 'orange' : ''
|
||||
}
|
||||
status={currentState.type === 'used' ? '' : 'active'}
|
||||
strokeColor={currentState.type === 'used' ? 'orange' : ''}
|
||||
style={{ width: '150px', marginBottom: '2px' }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@ -16,7 +16,6 @@ import CheckCircleIcon from '../../Icons/CheckCircleIcon.jsx'
|
||||
import ObjectProperty from '../common/ObjectProperty.jsx'
|
||||
import TemplatePreview from './TemplatePreview.jsx'
|
||||
import DataTree from './DataTree.jsx'
|
||||
//import { useMediaQuery } from 'react-responsive'
|
||||
|
||||
const TemplateEditor = ({
|
||||
objectData,
|
||||
@ -29,7 +28,6 @@ const TemplateEditor = ({
|
||||
const [testObjectViewMode, setTestObjectViewMode] = useState('Tree')
|
||||
const [previewMessage, setPreviewMessage] = useState('No issues found.')
|
||||
const [previewError, setPreviewError] = useState(false)
|
||||
//const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
|
||||
const handlePreviewMessage = (message, isError) => {
|
||||
setPreviewMessage(message)
|
||||
@ -38,7 +36,7 @@ const TemplateEditor = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Splitter className={'farmcontrol-splitter'} vertical={true}>
|
||||
<Splitter className={'farmcontrol-splitter'}>
|
||||
{collapseState.preview == true && (
|
||||
<Splitter.Panel style={{ height: '100%' }}>
|
||||
<Card
|
||||
|
||||
@ -1,316 +0,0 @@
|
||||
import { Input, Flex, List, Typography, Modal, Tag } from 'antd'
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
|
||||
const ActionsModalContext = createContext()
|
||||
|
||||
// Remove the "action" query param from a URL so we don't navigate to the same URL again
|
||||
const stripActionParam = (pathname, search) => {
|
||||
const params = new URLSearchParams(search)
|
||||
params.delete('action')
|
||||
const query = params.toString()
|
||||
return pathname + (query ? `?${query}` : '')
|
||||
}
|
||||
|
||||
// Flatten nested actions (including children) into a single list
|
||||
const flattenActions = (actions, parentLabel = '') => {
|
||||
if (!Array.isArray(actions)) return []
|
||||
|
||||
const flat = []
|
||||
|
||||
actions.forEach((action) => {
|
||||
if (!action || action.type === 'divider') {
|
||||
return
|
||||
}
|
||||
|
||||
const hasUrl = typeof action.url === 'function'
|
||||
const hasChildren =
|
||||
Array.isArray(action.children) && action.children.length > 0
|
||||
|
||||
const currentLabel = action.label || action.name || ''
|
||||
const fullLabel = parentLabel
|
||||
? `${parentLabel} / ${currentLabel}`
|
||||
: currentLabel
|
||||
|
||||
// Only push actions that are actually runnable
|
||||
if (hasUrl) {
|
||||
flat.push({
|
||||
...action,
|
||||
key: action.key || action.name || fullLabel,
|
||||
fullLabel
|
||||
})
|
||||
}
|
||||
|
||||
if (hasChildren) {
|
||||
flat.push(...flattenActions(action.children, fullLabel))
|
||||
}
|
||||
})
|
||||
|
||||
return flat
|
||||
}
|
||||
|
||||
const ActionsModalProvider = ({ children }) => {
|
||||
const { Text } = Typography
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [query, setQuery] = useState('')
|
||||
const [context, setContext] = useState({
|
||||
id: null,
|
||||
type: null,
|
||||
objectData: null
|
||||
})
|
||||
|
||||
const inputRef = useRef(null)
|
||||
|
||||
const showActionsModal = (id, type, objectData = null) => {
|
||||
setContext({ id, type, objectData })
|
||||
setQuery('')
|
||||
setVisible(true)
|
||||
}
|
||||
|
||||
const hideActionsModal = () => {
|
||||
setVisible(false)
|
||||
setQuery('')
|
||||
}
|
||||
|
||||
// Focus and select text in input when modal becomes visible
|
||||
useEffect(() => {
|
||||
// Use a small timeout to ensure the modal is fully rendered and visible
|
||||
setTimeout(() => {
|
||||
if (visible) {
|
||||
console.log('visible', visible)
|
||||
console.log('inputRef.current', inputRef.current)
|
||||
if (visible && inputRef.current) {
|
||||
console.log('focusing input')
|
||||
const input = inputRef.current.input
|
||||
if (input) {
|
||||
input.focus()
|
||||
input.select() // Select all text
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 50)
|
||||
}, [visible])
|
||||
|
||||
const model = context.type ? getModelByName(context.type) : null
|
||||
const ModelIcon = model?.icon || null
|
||||
const modelLabel = model?.label || model?.name || ''
|
||||
|
||||
const flattenedActions = useMemo(
|
||||
() => flattenActions(model?.actions || []),
|
||||
[model]
|
||||
)
|
||||
|
||||
const currentUrlWithoutActions = stripActionParam(
|
||||
location.pathname,
|
||||
location.search
|
||||
)
|
||||
|
||||
const getActionDisabled = (action) => {
|
||||
const { id, objectData } = context
|
||||
if (!action) return true
|
||||
|
||||
let disabled = false
|
||||
const url = action.url ? action.url(id) : undefined
|
||||
|
||||
// Match ObjectActions default disabling behaviour
|
||||
if (url && url === currentUrlWithoutActions) {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
if (typeof action.disabled !== 'undefined') {
|
||||
if (typeof action.disabled === 'function') {
|
||||
disabled = action.disabled(objectData)
|
||||
} else {
|
||||
disabled = action.disabled
|
||||
}
|
||||
}
|
||||
|
||||
return disabled
|
||||
}
|
||||
|
||||
const getVisibleDisabled = (action) => {
|
||||
const { objectData } = context
|
||||
if (!action) return true
|
||||
|
||||
let visible = true
|
||||
|
||||
if (typeof action.visible !== 'undefined') {
|
||||
if (typeof action.visible === 'function') {
|
||||
visible = action.visible(objectData)
|
||||
} else {
|
||||
visible = action.visible
|
||||
}
|
||||
}
|
||||
|
||||
return visible
|
||||
}
|
||||
|
||||
const normalizedQuery = query.trim().toLowerCase()
|
||||
|
||||
const filteredActions = flattenedActions.filter((action) => {
|
||||
if (!normalizedQuery) return true
|
||||
|
||||
const haystack = [
|
||||
action.fullLabel || '',
|
||||
action.label || '',
|
||||
action.name || '',
|
||||
modelLabel
|
||||
]
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
return haystack.includes(normalizedQuery)
|
||||
})
|
||||
|
||||
const runAction = (action) => {
|
||||
if (!action || typeof action.url !== 'function') return
|
||||
if (getActionDisabled(action)) return
|
||||
|
||||
const { id } = context
|
||||
const targetUrl = action.url(id)
|
||||
if (targetUrl && targetUrl !== '#') {
|
||||
navigate(targetUrl)
|
||||
hideActionsModal()
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (!filteredActions.length) return
|
||||
|
||||
// Enter triggers first visible action
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
const first = filteredActions[0]
|
||||
runAction(first)
|
||||
return
|
||||
}
|
||||
|
||||
// Number keys 1-9 trigger corresponding actions
|
||||
if (/^[1-9]$/.test(e.key)) {
|
||||
e.preventDefault()
|
||||
const index = parseInt(e.key, 10)
|
||||
if (index < filteredActions.length) {
|
||||
const action = filteredActions[index]
|
||||
runAction(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionsModalContext.Provider value={{ showActionsModal }}>
|
||||
<Modal
|
||||
open={visible}
|
||||
onCancel={hideActionsModal}
|
||||
closeIcon={null}
|
||||
footer={null}
|
||||
width={700}
|
||||
styles={{ content: { padding: 0 } }}
|
||||
destroyOnClose={true}
|
||||
>
|
||||
<Flex vertical>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
addonBefore={
|
||||
<Text style={{ fontSize: '20px' }}>
|
||||
<ModelIcon />
|
||||
</Text>
|
||||
}
|
||||
placeholder='Search actions...'
|
||||
size='large'
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
|
||||
{filteredActions.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
marginLeft: '14px',
|
||||
marginRight: '14px'
|
||||
}}
|
||||
>
|
||||
<List
|
||||
dataSource={filteredActions.filter((action) =>
|
||||
getVisibleDisabled(action)
|
||||
)}
|
||||
renderItem={(action, index) => {
|
||||
const Icon = action.icon
|
||||
const disabled = getActionDisabled(action)
|
||||
|
||||
let shortcutText = ''
|
||||
if (index === 0) {
|
||||
shortcutText = 'ENTER'
|
||||
} else if (index <= 9) {
|
||||
shortcutText = index.toString()
|
||||
}
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
onClick={() => !disabled && runAction(action)}
|
||||
style={{
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
opacity: disabled ? 0.5 : 1
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
gap='middle'
|
||||
style={{ width: '100%' }}
|
||||
align='center'
|
||||
justify='space-between'
|
||||
>
|
||||
<Flex
|
||||
gap='small'
|
||||
align='center'
|
||||
style={{ flexGrow: 1, minWidth: 0 }}
|
||||
>
|
||||
{Icon ? <Icon style={{ fontSize: '18px' }} /> : null}
|
||||
<Flex vertical style={{ flexGrow: 1, minWidth: 0 }}>
|
||||
<Text
|
||||
ellipsis
|
||||
style={{
|
||||
maxWidth: 320,
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{action.fullLabel || action.label || action.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex gap='small' align='center'>
|
||||
{action.danger && <Tag color='red'>Danger</Tag>}
|
||||
{shortcutText && <Text keyboard>{shortcutText}</Text>}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</List.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
</Modal>
|
||||
{children}
|
||||
</ActionsModalContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
ActionsModalProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired
|
||||
}
|
||||
|
||||
const useActionsModal = () => useContext(ActionsModalContext)
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { ActionsModalProvider, ActionsModalContext, useActionsModal }
|
||||
@ -70,11 +70,6 @@ const ApiServerProvider = ({ children }) => {
|
||||
[userProfile?._id]
|
||||
)
|
||||
|
||||
const clearSubscriptions = useCallback(() => {
|
||||
subscribedCallbacksRef.current.clear()
|
||||
subscribedLockCallbacksRef.current.clear()
|
||||
}, [])
|
||||
|
||||
const connectToServer = useCallback(() => {
|
||||
if (token && authenticated == true) {
|
||||
logger.debug('Token is available, connecting to api server...')
|
||||
@ -106,7 +101,6 @@ const ApiServerProvider = ({ children }) => {
|
||||
newSocket.on('disconnect', () => {
|
||||
logger.debug('Api Server disconnected')
|
||||
setError('Api Server disconnected')
|
||||
clearSubscriptions()
|
||||
setConnected(false)
|
||||
})
|
||||
|
||||
@ -114,10 +108,16 @@ const ApiServerProvider = ({ children }) => {
|
||||
logger.error('Api Server connection error:', err)
|
||||
messageApi.error('Api Server connection error: ' + err.message)
|
||||
setError('Api Server connection error')
|
||||
clearSubscriptions()
|
||||
setConnected(false)
|
||||
})
|
||||
|
||||
newSocket.on('bridge.notification', (data) => {
|
||||
notificationApi[data.type]({
|
||||
title: data.title,
|
||||
message: data.message
|
||||
})
|
||||
})
|
||||
|
||||
newSocket.on('error', (err) => {
|
||||
logger.error('Api Server error:', err)
|
||||
setError('Api Server error')
|
||||
@ -445,7 +445,6 @@ const ApiServerProvider = ({ children }) => {
|
||||
(id, objectType, eventType, callback) => {
|
||||
if (socketRef.current && socketRef.current.connected == true) {
|
||||
const callbacksRefKey = `${objectType}:${id}:events:${eventType}`
|
||||
|
||||
// Remove callback from the subscribed callbacks map
|
||||
if (subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
||||
const callbacks = subscribedCallbacksRef.current
|
||||
@ -453,7 +452,6 @@ const ApiServerProvider = ({ children }) => {
|
||||
.filter((cb) => cb !== callback)
|
||||
if (callbacks.length === 0) {
|
||||
subscribedCallbacksRef.current.delete(callbacksRefKey)
|
||||
console.log('Unsubscribing from object event:', callbacksRefKey)
|
||||
socketRef.current.emit('unsubscribeObjectEvent', {
|
||||
_id: id,
|
||||
objectType,
|
||||
@ -481,7 +479,6 @@ const ApiServerProvider = ({ children }) => {
|
||||
subscribedCallbacksRef.current.get(callbacksRefKey).length
|
||||
|
||||
if (callbacksLength <= 0) {
|
||||
console.log('Subscribing to object event:', callbacksRefKey)
|
||||
socketRef.current.emit(
|
||||
'subscribeToObjectEvent',
|
||||
{
|
||||
@ -935,11 +932,7 @@ const ApiServerProvider = ({ children }) => {
|
||||
}
|
||||
|
||||
// Upload file to the API
|
||||
const uploadFile = async (
|
||||
file,
|
||||
additionalData = {},
|
||||
progressCallback = null
|
||||
) => {
|
||||
const uploadFile = async (file, additionalData = {}) => {
|
||||
const uploadUrl = `${config.backendUrl}/files`
|
||||
logger.debug('Uploading file:', file.name, 'to:', uploadUrl)
|
||||
|
||||
@ -962,9 +955,6 @@ const ApiServerProvider = ({ children }) => {
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
)
|
||||
logger.debug(`Upload progress: ${percentCompleted}%`)
|
||||
if (progressCallback) {
|
||||
progressCallback(percentCompleted)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -973,7 +963,7 @@ const ApiServerProvider = ({ children }) => {
|
||||
} catch (err) {
|
||||
console.error('File upload error:', err)
|
||||
showError(err, () => {
|
||||
uploadFile(file, additionalData, progressCallback)
|
||||
uploadFile(file, additionalData)
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { message } from 'antd'
|
||||
|
||||
const MessageContext = createContext()
|
||||
|
||||
export const MessageProvider = ({ children }) => {
|
||||
const [msgApi, contextHolder] = message.useMessage()
|
||||
|
||||
const showMessage = (type, content, options = {}) => {
|
||||
return msgApi.open({
|
||||
type,
|
||||
content,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
const showSuccess = (content, options = {}) =>
|
||||
showMessage('success', content, options)
|
||||
const showInfo = (content, options = {}) =>
|
||||
showMessage('info', content, options)
|
||||
const showWarning = (content, options = {}) =>
|
||||
showMessage('warning', content, options)
|
||||
const showError = (content, options = {}) =>
|
||||
showMessage('error', content, options)
|
||||
const showLoading = (content, options = {}) =>
|
||||
showMessage('loading', content, options)
|
||||
|
||||
return (
|
||||
<MessageContext.Provider
|
||||
value={{
|
||||
msgApi,
|
||||
showSuccess,
|
||||
showInfo,
|
||||
showWarning,
|
||||
showError,
|
||||
showLoading
|
||||
}}
|
||||
>
|
||||
{contextHolder}
|
||||
{children}
|
||||
</MessageContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
MessageProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired
|
||||
}
|
||||
|
||||
export const useMessageContext = () => {
|
||||
const context = useContext(MessageContext)
|
||||
if (!context) {
|
||||
throw new Error('useMessageContext must be used within a MessageProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export { MessageContext }
|
||||
@ -1,8 +0,0 @@
|
||||
import Icon from '@ant-design/icons'
|
||||
import CustomIconSvg from '../../../assets/icons/purchaseordericon.svg?react'
|
||||
|
||||
const PurchaseOrderIcon = (props) => (
|
||||
<Icon component={CustomIconSvg} {...props} />
|
||||
)
|
||||
|
||||
export default PurchaseOrderIcon
|
||||
@ -15,7 +15,6 @@ import { StockEvent } from './models/StockEvent'
|
||||
import { StockAudit } from './models/StockAudit'
|
||||
import { PartStock } from './models/PartStock'
|
||||
import { ProductStock } from './models/ProductStock'
|
||||
import { PurchaseOrder } from './models/PurchaseOrder'
|
||||
import { AuditLog } from './models/AuditLog'
|
||||
import { User } from './models/User'
|
||||
import { NoteType } from './models/NoteType'
|
||||
@ -44,7 +43,6 @@ export const objectModels = [
|
||||
StockAudit,
|
||||
PartStock,
|
||||
ProductStock,
|
||||
PurchaseOrder,
|
||||
AuditLog,
|
||||
User,
|
||||
NoteType,
|
||||
@ -74,7 +72,6 @@ export {
|
||||
StockAudit,
|
||||
PartStock,
|
||||
ProductStock,
|
||||
PurchaseOrder,
|
||||
AuditLog,
|
||||
User,
|
||||
NoteType,
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import DocumentJobIcon from '../../components/Icons/DocumentJobIcon'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
@ -35,31 +33,7 @@ export const DocumentJob = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentjobs/info?documentJobId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentjobs/info?documentJobId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentjobs/info?documentJobId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/management/documentjobs/info?documentJobId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: ['name', '_id', 'state', 'createdAt', 'updatedAt'],
|
||||
|
||||
@ -2,8 +2,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import DocumentPrinterIcon from '../../components/Icons/DocumentPrinterIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
|
||||
export const DocumentPrinter = {
|
||||
name: 'documentPrinter',
|
||||
@ -34,31 +32,7 @@ export const DocumentPrinter = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentprinters/info?documentPrinterId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentprinters/info?documentPrinterId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentprinters/info?documentPrinterId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/management/documentprinters/info?documentPrinterId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
@ -127,21 +101,6 @@ export const DocumentPrinter = {
|
||||
type: 'bool',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'vendor',
|
||||
label: 'Vendor',
|
||||
type: 'object',
|
||||
objectType: 'vendor',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'vendor._id',
|
||||
label: 'Vendor ID',
|
||||
type: 'id',
|
||||
objectType: 'vendor',
|
||||
showHyperlink: true,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'host',
|
||||
label: 'Host',
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import DocumentSizeIcon from '../../components/Icons/DocumentSizeIcon'
|
||||
|
||||
export const DocumentSize = {
|
||||
@ -34,31 +32,7 @@ export const DocumentSize = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentsizes/info?documentSizeId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentsizes/info?documentSizeId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documentsizes/info?documentSizeId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/management/documentsizes/info?documentSizeId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import DesignIcon from '../../components/Icons/DesignIcon'
|
||||
import DocumentTemplateIcon from '../../components/Icons/DocumentTemplateIcon'
|
||||
|
||||
@ -43,31 +41,7 @@ export const DocumentTemplate = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documenttemplates/info?documentTemplateId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documenttemplates/info?documentTemplateId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/documenttemplates/info?documentTemplateId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/management/documenttemplates/info?documentTemplateId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
|
||||
@ -2,8 +2,6 @@ import EditIcon from '../../components/Icons/EditIcon'
|
||||
import FilamentIcon from '../../components/Icons/FilamentIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
|
||||
export const Filament = {
|
||||
name: 'filament',
|
||||
@ -32,31 +30,7 @@ export const Filament = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filaments/info?filamentId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filaments/info?filamentId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/filaments/info?filamentId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/management/filaments/info?filamentId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
|
||||
@ -2,8 +2,6 @@ import DownloadIcon from '../../components/Icons/DownloadIcon'
|
||||
import FileIcon from '../../components/Icons/FileIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
|
||||
@ -33,31 +31,7 @@ export const File = {
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) => `/dashboard/management/files/info?fileId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/files/info?fileId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/files/info?fileId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
url: (_id) => `/dashboard/management/files/info?fileId=${_id}&action=edit`
|
||||
},
|
||||
{
|
||||
name: 'download',
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import DownloadIcon from '../../components/Icons/DownloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import GCodeFileIcon from '../../components/Icons/GCodeFileIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
@ -41,31 +39,7 @@ export const GCodeFile = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/production/gcodefiles/info?gcodeFileId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/production/gcodefiles/info?gcodeFileId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/production/gcodefiles/info?gcodeFileId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/production/gcodefiles/info?gcodeFileId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
|
||||
@ -153,11 +127,9 @@ export const GCodeFile = {
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
type: 'number',
|
||||
roundNumber: 2,
|
||||
value: (objectData) => {
|
||||
return (
|
||||
objectData?.file?.metaData?.filamentUsedG *
|
||||
(objectData?.filament?.cost / 1000)
|
||||
objectData?.file?.metaData?.filamentUsedG * objectData?.filament?.cost
|
||||
)
|
||||
},
|
||||
readOnly: true,
|
||||
@ -224,51 +196,6 @@ export const GCodeFile = {
|
||||
label: 'Print Profile',
|
||||
type: 'text',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'parts',
|
||||
label: 'Parts',
|
||||
type: 'objectChildren',
|
||||
objectType: 'part',
|
||||
properties: [
|
||||
{
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
type: 'object',
|
||||
objectType: 'part',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'part._id',
|
||||
label: 'Part ID',
|
||||
type: 'id',
|
||||
objectType: 'part',
|
||||
showHyperlink: true,
|
||||
value: (objectData) => {
|
||||
return objectData?.part?._id
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
label: 'Quantity',
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
rollups: [
|
||||
{
|
||||
name: 'totalQuantity',
|
||||
label: 'Total',
|
||||
type: 'number',
|
||||
property: 'quantity',
|
||||
value: (objectData) => {
|
||||
return objectData?.parts?.reduce(
|
||||
(acc, part) => acc + part.quantity,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,8 +2,6 @@ import HostIcon from '../../components/Icons/HostIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import OTPIcon from '../../components/Icons/OTPIcon'
|
||||
|
||||
export const Host = {
|
||||
@ -40,32 +38,7 @@ export const Host = {
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/hosts/info?hostId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/hosts/info?hostId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/hosts/info?hostId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
url: (_id) => `/dashboard/management/hosts/info?hostId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: ['name', '_id', 'state', 'tags', 'connectedAt'],
|
||||
@ -81,8 +54,8 @@ export const Host = {
|
||||
showCopy: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
name: 'connectedAt',
|
||||
label: 'Connected At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
@ -94,12 +67,6 @@ export const Host = {
|
||||
columnWidth: 200,
|
||||
columnFixed: 'left'
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
@ -109,10 +76,10 @@ export const Host = {
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'connectedAt',
|
||||
label: 'Connected At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
name: 'active',
|
||||
label: 'Active',
|
||||
type: 'bool',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'online',
|
||||
@ -120,13 +87,6 @@ export const Host = {
|
||||
type: 'bool',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'active',
|
||||
label: 'Active',
|
||||
type: 'bool',
|
||||
required: true
|
||||
},
|
||||
|
||||
{
|
||||
name: 'deviceInfo.os',
|
||||
label: 'Operating System',
|
||||
@ -198,14 +158,6 @@ export const Host = {
|
||||
label: 'Tags',
|
||||
type: 'tags',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'files',
|
||||
label: 'Files',
|
||||
type: 'objectList',
|
||||
objectType: 'file',
|
||||
required: false,
|
||||
readOnly: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -56,12 +56,6 @@ export const Job = {
|
||||
objectType: 'job',
|
||||
showCopy: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
@ -71,39 +65,7 @@ export const Job = {
|
||||
showProgress: true,
|
||||
showId: false,
|
||||
showQuantity: false,
|
||||
columnWidth: 250,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
label: 'Quantity',
|
||||
type: 'number',
|
||||
columnWidth: 125,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'startedAt',
|
||||
label: 'Started At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'printers',
|
||||
label: 'Printers',
|
||||
type: 'objectList',
|
||||
objectType: 'printer',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'finishedAt',
|
||||
label: 'Finished At',
|
||||
type: 'dateTime',
|
||||
columnWidth: 150,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
@ -120,6 +82,33 @@ export const Job = {
|
||||
type: 'id',
|
||||
objectType: 'gcodeFile',
|
||||
showHyperlink: true
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
label: 'Quantity',
|
||||
type: 'number',
|
||||
columnWidth: 125,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'startedAt',
|
||||
label: 'Started At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'printers',
|
||||
label: 'Printers',
|
||||
type: 'objectList',
|
||||
objectType: 'printer',
|
||||
required: true,
|
||||
span: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,8 +2,6 @@ import NoteTypeIcon from '../../components/Icons/NoteTypeIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
|
||||
export const NoteType = {
|
||||
name: 'noteType',
|
||||
@ -32,31 +30,7 @@ export const NoteType = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/notetypes/info?noteTypeId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/notetypes/info?noteTypeId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/notetypes/info?noteTypeId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/management/notetypes/info?noteTypeId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: ['name', '_id', 'color', 'active', 'createdAt', 'updatedAt'],
|
||||
|
||||
@ -2,8 +2,6 @@ import EditIcon from '../../components/Icons/EditIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import PartIcon from '../../components/Icons/PartIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
|
||||
export const Part = {
|
||||
name: 'part',
|
||||
@ -31,32 +29,7 @@ export const Part = {
|
||||
label: 'Edit',
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/parts/info?partId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/parts/info?partId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/parts/info?partId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
url: (_id) => `/dashboard/management/parts/info?partId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
@ -98,6 +71,22 @@ export const Part = {
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
|
||||
{
|
||||
name: 'product',
|
||||
label: 'Product',
|
||||
type: 'object',
|
||||
required: true,
|
||||
objectType: 'product'
|
||||
},
|
||||
{
|
||||
name: 'product._id',
|
||||
label: 'Product ID',
|
||||
type: 'id',
|
||||
readOnly: true,
|
||||
showHyperlink: true,
|
||||
objectType: 'product'
|
||||
},
|
||||
{
|
||||
name: 'vendor',
|
||||
label: 'Vendor',
|
||||
|
||||
@ -2,7 +2,7 @@ import PartStockIcon from '../../components/Icons/PartStockIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
|
||||
export const PartStock = {
|
||||
name: 'partStock',
|
||||
name: 'partstock',
|
||||
label: 'Part Stock',
|
||||
prefix: 'PTS',
|
||||
icon: PartStockIcon,
|
||||
@ -13,124 +13,8 @@ export const PartStock = {
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon,
|
||||
url: (_id) => `/dashboard/inventory/partstocks/info?partStockId=${_id}`
|
||||
url: (_id) => `/dashboard/management/partstocks/info?partStockId=${_id}`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/inventory/partstocks/info?partStockId=${id}`,
|
||||
filters: ['_id', 'part', 'startingQuantity', 'currentQuantity'],
|
||||
sorters: ['part', 'startingQuantity', 'currentQuantity'],
|
||||
columns: [
|
||||
'_id',
|
||||
'state',
|
||||
'startingQuantity',
|
||||
'currentQuantity',
|
||||
'part',
|
||||
'part._id',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
label: 'ID',
|
||||
type: 'id',
|
||||
objectType: 'partStock',
|
||||
showCopy: true,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
type: 'state',
|
||||
readOnly: true,
|
||||
columnWidth: 120
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'sourceType',
|
||||
label: 'Source Type',
|
||||
type: 'objectType',
|
||||
readOnly: false,
|
||||
columnWidth: 200,
|
||||
required: true,
|
||||
masterFilter: ['subJob']
|
||||
},
|
||||
|
||||
{
|
||||
name: 'consumedAt',
|
||||
label: 'Consumed At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
type: 'object',
|
||||
objectType: 'part',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'part._id',
|
||||
label: 'Part ID',
|
||||
type: 'id',
|
||||
objectType: 'part',
|
||||
readOnly: true,
|
||||
showHyperlink: true
|
||||
},
|
||||
|
||||
{
|
||||
name: 'source',
|
||||
label: 'Source',
|
||||
type: 'object',
|
||||
readOnly: false,
|
||||
required: true,
|
||||
columnWidth: 200,
|
||||
objectType: (objectData) => {
|
||||
return objectData?.sourceType
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'source._id',
|
||||
label: 'Source ID',
|
||||
type: 'id',
|
||||
readOnly: true,
|
||||
columnWidth: 200,
|
||||
objectType: (objectData) => {
|
||||
return objectData?.sourceType
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'currentQuantity',
|
||||
label: 'Current Quantity',
|
||||
type: 'number',
|
||||
readOnly: true,
|
||||
columnWidth: 200,
|
||||
required: true,
|
||||
value: (objectData) => {
|
||||
if (objectData?.state?.type === 'new') {
|
||||
return objectData?.startingQuantity
|
||||
} else {
|
||||
return objectData.currentQuantity
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'startingQuantity',
|
||||
label: 'Starting Quantity',
|
||||
type: 'number',
|
||||
columnWidth: 200,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
url: (id) => `/dashboard/management/partstocks/info?partStockId=${id}`
|
||||
}
|
||||
|
||||
@ -2,14 +2,10 @@ import PrinterIcon from '../../components/Icons/PrinterIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import PlayCircleIcon from '../../components/Icons/PlayCircleIcon'
|
||||
import PauseCircleIcon from '../../components/Icons/PauseCircleIcon'
|
||||
import StopCircleIcon from '../../components/Icons/StopCircleIcon'
|
||||
import FilamentStockIcon from '../../components/Icons/FilamentStockIcon'
|
||||
import ControlIcon from '../../components/Icons/ControlIcon'
|
||||
|
||||
export const Printer = {
|
||||
name: 'printer',
|
||||
label: 'Printer',
|
||||
@ -36,7 +32,7 @@ export const Printer = {
|
||||
name: 'control',
|
||||
label: 'Control',
|
||||
row: true,
|
||||
icon: ControlIcon,
|
||||
icon: PlayCircleIcon,
|
||||
url: (_id) => `/dashboard/production/printers/control?printerId=${_id}`
|
||||
},
|
||||
{
|
||||
@ -45,31 +41,7 @@ export const Printer = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/production/printers/info?printerId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/production/printers/info?printerId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/production/printers/info?printerId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/production/printers/info?printerId=${_id}&action=edit`
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
@ -126,13 +98,12 @@ export const Printer = {
|
||||
label: 'Start Queue',
|
||||
icon: PlayCircleIcon,
|
||||
disabled: (objectData) => {
|
||||
console.log(objectData?.queue?.length)
|
||||
console.log(objectData?.subJobs?.length)
|
||||
return (
|
||||
objectData?.state?.type == 'error' ||
|
||||
objectData?.state?.type == 'printing' ||
|
||||
objectData?.state?.type == 'paused' ||
|
||||
objectData?.queue?.length == 0 ||
|
||||
objectData?.queue?.length == undefined
|
||||
objectData?.subJobs?.length == 0 ||
|
||||
objectData?.subJobs?.length == undefined
|
||||
)
|
||||
},
|
||||
url: (_id) =>
|
||||
@ -154,7 +125,7 @@ export const Printer = {
|
||||
label: 'Resume Job',
|
||||
icon: PlayCircleIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type != 'paused'
|
||||
return objectData?.state?.type != 'printing'
|
||||
},
|
||||
url: (_id) =>
|
||||
`/dashboard/production/printers/control?printerId=${_id}&action=resumeJob`
|
||||
@ -166,7 +137,7 @@ export const Printer = {
|
||||
disabled: (objectData) => {
|
||||
return (
|
||||
objectData?.state?.type != 'printing' &&
|
||||
objectData?.state?.type != 'paused'
|
||||
objectData?.state?.type != 'error'
|
||||
)
|
||||
},
|
||||
url: (_id) =>
|
||||
@ -178,37 +149,20 @@ export const Printer = {
|
||||
name: 'filamentStock',
|
||||
label: 'Filament Stock',
|
||||
icon: FilamentStockIcon,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.online == false
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'loadFilamentStock',
|
||||
label: 'Load Filament Stock',
|
||||
icon: FilamentStockIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/production/printers/control?printerId=${_id}&action=loadFilamentStock`,
|
||||
disabled: (objectData) => {
|
||||
return (
|
||||
objectData?.state?.type == 'printing' ||
|
||||
objectData?.state?.type == 'error' ||
|
||||
objectData?.currentFilamentStock != null
|
||||
)
|
||||
}
|
||||
`/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`,
|
||||
disabled: (objectData) => {
|
||||
return (
|
||||
objectData?.state?.type == 'printing' ||
|
||||
objectData?.state?.type == 'error' ||
|
||||
objectData?.currentFilamentStock == null
|
||||
)
|
||||
}
|
||||
`/dashboard/production/printers/control?printerId=${_id}&action=unloadFilamentStock`
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -260,8 +214,7 @@ export const Printer = {
|
||||
type: 'state',
|
||||
objectType: 'printer',
|
||||
showName: false,
|
||||
readOnly: true,
|
||||
columnWidth: 250
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'connectedAt',
|
||||
@ -401,7 +354,7 @@ export const Printer = {
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'queue',
|
||||
name: 'subJobs',
|
||||
label: 'Queue',
|
||||
type: 'objectList',
|
||||
objectType: 'subJob',
|
||||
|
||||
@ -2,8 +2,6 @@ import ProductIcon from '../../components/Icons/ProductIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
|
||||
export const Product = {
|
||||
name: 'product',
|
||||
@ -32,31 +30,7 @@ export const Product = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/products/info?productId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/products/info?productId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/products/info?productId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/management/products/info?productId=${_id}&action=edit`
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
@ -155,51 +129,6 @@ export const Product = {
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.1
|
||||
},
|
||||
{
|
||||
name: 'parts',
|
||||
label: 'Parts',
|
||||
type: 'objectChildren',
|
||||
objectType: 'part',
|
||||
properties: [
|
||||
{
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
type: 'object',
|
||||
objectType: 'part',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'part._id',
|
||||
label: 'Part ID',
|
||||
type: 'id',
|
||||
objectType: 'part',
|
||||
showHyperlink: true,
|
||||
value: (objectData) => {
|
||||
return objectData?.part?._id
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
label: 'Quantity',
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
rollups: [
|
||||
{
|
||||
name: 'totalQuantity',
|
||||
label: 'Total',
|
||||
type: 'number',
|
||||
property: 'quantity',
|
||||
value: (objectData) => {
|
||||
return objectData?.parts?.reduce(
|
||||
(acc, part) => acc + part.quantity,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import PurchaseOrderIcon from '../../components/Icons/PurchaseOrderIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
|
||||
export const PurchaseOrder = {
|
||||
name: 'purchaseorder',
|
||||
label: 'Product Stock',
|
||||
prefix: 'PDS',
|
||||
icon: PurchaseOrderIcon,
|
||||
actions: [
|
||||
{
|
||||
name: 'info',
|
||||
label: 'Info',
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/purchaseorders/info?purchaseOrderId=${_id}`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/purchaseorders/info?purchaseOrderId=${id}`
|
||||
}
|
||||
@ -2,7 +2,7 @@ import StockAuditIcon from '../../components/Icons/StockAuditIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
|
||||
export const StockAudit = {
|
||||
name: 'stockAudit',
|
||||
name: 'stockaudit',
|
||||
label: 'Stock Audit',
|
||||
prefix: 'SAU',
|
||||
icon: StockAuditIcon,
|
||||
@ -16,38 +16,5 @@ export const StockAudit = {
|
||||
url: (_id) => `/dashboard/inventory/stockaudits/info?stockAuditId=${_id}`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/inventory/stockaudits/info?stockAuditId=${id}`,
|
||||
columns: ['_id', 'state', 'createdAt', 'updatedAt'],
|
||||
filters: ['_id'],
|
||||
sorters: ['createdAt', 'updatedAt'],
|
||||
group: ['state'],
|
||||
properties: [
|
||||
{
|
||||
name: '_id',
|
||||
label: 'ID',
|
||||
type: 'id',
|
||||
objectType: 'stockAudit',
|
||||
showCopy: true,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
type: 'state',
|
||||
readOnly: true,
|
||||
columnWidth: 120
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
}
|
||||
]
|
||||
url: (id) => `/dashboard/inventory/stockaudits/info?stockAuditId=${id}`
|
||||
}
|
||||
|
||||
@ -1,33 +1,11 @@
|
||||
import SubJobIcon from '../../components/Icons/SubJobIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
|
||||
export const SubJob = {
|
||||
name: 'subJob',
|
||||
label: 'Sub Job',
|
||||
prefix: 'SJB',
|
||||
icon: SubJobIcon,
|
||||
actions: [
|
||||
{
|
||||
name: 'info',
|
||||
label: 'Info',
|
||||
default: true,
|
||||
row: true,
|
||||
icon: InfoCircleIcon,
|
||||
url: (_id) => `/dashboard/production/subjobs/info?subJobId=${_id}`
|
||||
},
|
||||
{
|
||||
name: 'cancel',
|
||||
label: 'Cancel Sub Job',
|
||||
row: true,
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/production/subjobs/info?subJobId=${_id}&action=cancel`,
|
||||
disabled: (objectData) => {
|
||||
return objectData?.state?.type !== 'queued'
|
||||
}
|
||||
}
|
||||
],
|
||||
actions: [],
|
||||
columns: ['_id', 'printer', 'printer._id', 'job._id', 'state', 'createdAt'],
|
||||
filters: ['state', '_id', 'job._id', 'printer._id'],
|
||||
sorters: ['createdAt', 'state'],
|
||||
@ -41,62 +19,6 @@ export const SubJob = {
|
||||
columnWidth: 140,
|
||||
showCopy: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
type: 'state',
|
||||
objectType: 'subJob',
|
||||
showStatus: true,
|
||||
showProgress: true,
|
||||
showId: false,
|
||||
showQuantity: false,
|
||||
columnWidth: 250,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Updated At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'job._id',
|
||||
label: 'Job ID',
|
||||
type: 'id',
|
||||
columnWidth: 140,
|
||||
showHyperlink: true,
|
||||
objectType: 'job'
|
||||
},
|
||||
|
||||
{
|
||||
name: 'startedAt',
|
||||
label: 'Started At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
|
||||
{
|
||||
name: 'moonrakerJobId',
|
||||
label: 'Moonraker Job ID',
|
||||
type: 'miscId',
|
||||
columnWidth: 140,
|
||||
showCopy: true
|
||||
},
|
||||
|
||||
{
|
||||
name: 'finishedAt',
|
||||
label: 'Finished At',
|
||||
type: 'dateTime',
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'printer',
|
||||
label: 'Printer',
|
||||
@ -112,6 +34,33 @@ export const SubJob = {
|
||||
columnFixed: 'left',
|
||||
showHyperlink: true,
|
||||
objectType: 'printer'
|
||||
},
|
||||
{
|
||||
name: 'job._id',
|
||||
label: 'Job ID',
|
||||
type: 'id',
|
||||
columnWidth: 140,
|
||||
showHyperlink: true,
|
||||
objectType: 'job'
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
type: 'state',
|
||||
objectType: 'subJob',
|
||||
showStatus: true,
|
||||
showProgress: true,
|
||||
showId: false,
|
||||
showQuantity: false,
|
||||
columnWidth: 125,
|
||||
readOnly: true
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
type: 'dateTime',
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import VendorIcon from '../../components/Icons/VendorIcon'
|
||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
import EditIcon from '../../components/Icons/EditIcon'
|
||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||
import ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||
import BinIcon from '../../components/Icons/BinIcon'
|
||||
|
||||
@ -33,31 +31,7 @@ export const Vendor = {
|
||||
row: true,
|
||||
icon: EditIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/vendors/info?vendorId=${_id}&action=edit`,
|
||||
visible: (objectData) => {
|
||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'finishEdit',
|
||||
label: 'Save Edits',
|
||||
icon: CheckIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/vendors/info?vendorId=${_id}&action=finishEdit`,
|
||||
visible: (objectData) => {
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'cancelEdit',
|
||||
label: 'Cancel Edits',
|
||||
icon: XMarkIcon,
|
||||
url: (_id) =>
|
||||
`/dashboard/management/vendors/info?vendorId=${_id}&action=cancelEdit`,
|
||||
visible: (objectData) => {
|
||||
console.log(objectData?._isEditing)
|
||||
return objectData?._isEditing && objectData?._isEditing == true
|
||||
}
|
||||
`/dashboard/management/vendors/info?vendorId=${_id}&action=edit`
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
|
||||
@ -3,7 +3,6 @@ import { Route } from 'react-router-dom'
|
||||
import FilamentStocks from '../components/Dashboard/Inventory/FilamentStocks.jsx'
|
||||
import FilamentStockInfo from '../components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx'
|
||||
import PartStocks from '../components/Dashboard/Inventory/PartStocks.jsx'
|
||||
import PartStockInfo from '../components/Dashboard/Inventory/PartStocks/PartStockInfo.jsx'
|
||||
import StockEvents from '../components/Dashboard/Inventory/StockEvents.jsx'
|
||||
import StockAudits from '../components/Dashboard/Inventory/StockAudits.jsx'
|
||||
import StockAuditInfo from '../components/Dashboard/Inventory/StockAudits/StockAuditInfo.jsx'
|
||||
@ -24,11 +23,6 @@ const InventoryRoutes = [
|
||||
path='inventory/partstocks'
|
||||
element={<PartStocks />}
|
||||
/>,
|
||||
<Route
|
||||
key='partstocks-info'
|
||||
path='inventory/partstocks/info'
|
||||
element={<PartStockInfo />}
|
||||
/>,
|
||||
<Route
|
||||
key='stockevents'
|
||||
path='inventory/stockevents'
|
||||
|
||||
@ -7,7 +7,6 @@ import PrinterInfo from '../components/Dashboard/Production/Printers/PrinterInfo
|
||||
import Jobs from '../components/Dashboard/Production/Jobs.jsx'
|
||||
import JobInfo from '../components/Dashboard/Production/Jobs/JobInfo.jsx'
|
||||
import SubJobs from '../components/Dashboard/Production/SubJobs.jsx'
|
||||
import SubJobInfo from '../components/Dashboard/Production/SubJobs/SubJobInfo.jsx'
|
||||
import GCodeFiles from '../components/Dashboard/Production/GCodeFiles'
|
||||
import GCodeFileInfo from '../components/Dashboard/Production/GCodeFiles/GCodeFileInfo.jsx'
|
||||
|
||||
@ -30,11 +29,6 @@ const ProductionRoutes = [
|
||||
/>,
|
||||
<Route key='jobs' path='production/jobs' element={<Jobs />} />,
|
||||
<Route key='subjobs' path='production/subjobs' element={<SubJobs />} />,
|
||||
<Route
|
||||
key='subjobs-info'
|
||||
path='production/subjobs/info'
|
||||
element={<SubJobInfo />}
|
||||
/>,
|
||||
<Route key='jobs-info' path='production/jobs/info' element={<JobInfo />} />,
|
||||
<Route
|
||||
key='gcodefiles'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user