Refactored components to replace DashboardTable with ObjectTable for improved consistency and functionality. Updated FilamentStockInfo, PartStocks, StockAudits, and other inventory management components to utilize NotesPanel instead of DashboardNotes. Removed unused DashboardNotes and DashboardTable components to streamline the codebase.
This commit is contained in:
parent
9ccf7faa2f
commit
fe85250838
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 132 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 153 KiB |
BIN
src/assets/logos/farmcontrolicon.afdesign
Normal file
BIN
src/assets/logos/farmcontrolicon.afdesign
Normal file
Binary file not shown.
BIN
src/assets/logos/farmcontrolicon.png
Normal file
BIN
src/assets/logos/farmcontrolicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 533 KiB |
BIN
src/assets/logos/farmcontroliconfill.afdesign
Normal file
BIN
src/assets/logos/farmcontroliconfill.afdesign
Normal file
Binary file not shown.
BIN
src/assets/logos/farmcontroliconfill.png
Normal file
BIN
src/assets/logos/farmcontroliconfill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 550 KiB |
@ -29,7 +29,7 @@ import TimeDisplay from '../common/TimeDisplay'
|
|||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import CheckIcon from '../../Icons/CheckIcon'
|
import CheckIcon from '../../Icons/CheckIcon'
|
||||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import ListIcon from '../../Icons/ListIcon'
|
import ListIcon from '../../Icons/ListIcon'
|
||||||
import GridIcon from '../../Icons/GridIcon'
|
import GridIcon from '../../Icons/GridIcon'
|
||||||
import useViewMode from '../hooks/useViewMode'
|
import useViewMode from '../hooks/useViewMode'
|
||||||
@ -326,7 +326,7 @@ const FilamentStocks = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/filamentstocks`}
|
url={`${config.backendUrl}/filamentstocks`}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import TimeDisplay from '../../common/TimeDisplay'
|
|||||||
import FilamentIcon from '../../../Icons/FilamentIcon'
|
import FilamentIcon from '../../../Icons/FilamentIcon'
|
||||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
|
|
||||||
import config from '../../../../config'
|
import config from '../../../../config'
|
||||||
import FilamentStockIcon from '../../../Icons/FilamentStockIcon'
|
import FilamentStockIcon from '../../../Icons/FilamentStockIcon'
|
||||||
@ -424,7 +424,7 @@ const FilamentStockInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={filamentStockId} />
|
<NotesPanel _id={filamentStockId} />
|
||||||
</Card>
|
</Card>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import PlusIcon from '../../Icons/PlusIcon'
|
|||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
import PartStockState from '../common/PartStockState'
|
import PartStockState from '../common/PartStockState'
|
||||||
import TimeDisplay from '../common/TimeDisplay'
|
import TimeDisplay from '../common/TimeDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ const PartStocks = () => {
|
|||||||
<Button>Actions</Button>
|
<Button>Actions</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Space>
|
</Space>
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
url={`${config.backendUrl}/partstocks`}
|
url={`${config.backendUrl}/partstocks`}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
|||||||
import PlusIcon from '../../Icons/PlusIcon'
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
import TimeDisplay from '../common/TimeDisplay'
|
import TimeDisplay from '../common/TimeDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ const StockAudits = () => {
|
|||||||
<Button>Actions</Button>
|
<Button>Actions</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Space>
|
</Space>
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
url={`${config.backendUrl}/stockaudits`}
|
url={`${config.backendUrl}/stockaudits`}
|
||||||
|
|||||||
@ -19,13 +19,13 @@ import PlusMinusIcon from '../../Icons/PlusMinusIcon'
|
|||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import CheckIcon from '../../Icons/CheckIcon'
|
import CheckIcon from '../../Icons/CheckIcon'
|
||||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import GridIcon from '../../Icons/GridIcon'
|
import GridIcon from '../../Icons/GridIcon'
|
||||||
import ListIcon from '../../Icons/ListIcon'
|
import ListIcon from '../../Icons/ListIcon'
|
||||||
import useViewMode from '../hooks/useViewMode'
|
import useViewMode from '../hooks/useViewMode'
|
||||||
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
import { getTypeMeta } from '../utils/Utils'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import StockEventIcon from '../../Icons/StockEventIcon'
|
import StockEventIcon from '../../Icons/StockEventIcon'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
@ -53,7 +53,7 @@ const StockEvents = () => {
|
|||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (type) => {
|
render: (type) => {
|
||||||
return <Text>{getTypeMeta(type?.toLowerCase()).title}</Text>
|
return <Text>{getModelByName(type).title}</Text>
|
||||||
},
|
},
|
||||||
filterDropdown: ({
|
filterDropdown: ({
|
||||||
setSelectedKeys,
|
setSelectedKeys,
|
||||||
@ -128,7 +128,7 @@ const StockEvents = () => {
|
|||||||
) : null}
|
) : null}
|
||||||
{record.subJob?.number ? (
|
{record.subJob?.number ? (
|
||||||
<IdDisplay
|
<IdDisplay
|
||||||
id={record.subJob.number.toString().padStart(6, '0')}
|
id={record.subJob._id}
|
||||||
longId={false}
|
longId={false}
|
||||||
type={'subjob'}
|
type={'subjob'}
|
||||||
/>
|
/>
|
||||||
@ -310,7 +310,7 @@ const StockEvents = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/stockevents`}
|
url={`${config.backendUrl}/stockevents`}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import IdDisplay from '../common/IdDisplay'
|
|||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
import TimeDisplay from '../common/TimeDisplay'
|
import TimeDisplay from '../common/TimeDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
import AuditLogIcon from '../../Icons/AuditLogIcon'
|
import AuditLogIcon from '../../Icons/AuditLogIcon'
|
||||||
@ -316,7 +316,7 @@ const AuditLogs = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/auditlogs`}
|
url={`${config.backendUrl}/auditlogs`}
|
||||||
|
|||||||
@ -7,17 +7,20 @@ import config from '../../../../config'
|
|||||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
import InfoCollapse from '../../common/InfoCollapse'
|
import InfoCollapse from '../../common/InfoCollapse'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import ViewButton from '../../common/ViewButton'
|
import ViewButton from '../../common/ViewButton'
|
||||||
|
|
||||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
import EditObjectForm from '../../common/EditObjectForm'
|
import EditObjectForm from '../../common/EditObjectForm'
|
||||||
import EditButtons from '../../common/EditButtons'
|
import EditButtons from '../../common/EditButtons'
|
||||||
import LockIndicator from './LockIndicator'
|
import LockIndicator from './LockIndicator'
|
||||||
|
import {
|
||||||
|
getModelProperties,
|
||||||
|
getPropertyValue
|
||||||
|
} from '../../../../database/ObjectModels'
|
||||||
|
|
||||||
const log = loglevel.getLogger('FilamentInfo')
|
const log = loglevel.getLogger('FilamentInfo')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
@ -115,101 +118,10 @@ const FilamentInfo = () => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
items={[
|
items={getModelProperties('filament').map((prop) => ({
|
||||||
{
|
...prop,
|
||||||
name: 'id',
|
value: getPropertyValue(objectData, prop.name)
|
||||||
label: 'ID',
|
}))}
|
||||||
value: objectData?._id,
|
|
||||||
type: 'id',
|
|
||||||
objectType: 'filament',
|
|
||||||
showCopy: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'createdAt',
|
|
||||||
label: 'Created At',
|
|
||||||
value: objectData?.createdAt,
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
value: objectData?.name,
|
|
||||||
required: true,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'updatedAt',
|
|
||||||
label: 'Updated At',
|
|
||||||
value: objectData?.updatedAt,
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vendor',
|
|
||||||
label: 'Vendor',
|
|
||||||
value: objectData?.vendor,
|
|
||||||
required: true,
|
|
||||||
type: 'object',
|
|
||||||
objectType: 'vendor'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vendorId',
|
|
||||||
label: 'Vendor ID',
|
|
||||||
value: objectData?.vendor?.id,
|
|
||||||
type: 'id',
|
|
||||||
objectType: 'vendor',
|
|
||||||
showCopy: true,
|
|
||||||
showHyperlink: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'type',
|
|
||||||
label: 'Material',
|
|
||||||
value: objectData?.type,
|
|
||||||
required: true,
|
|
||||||
type: 'material'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cost',
|
|
||||||
label: 'Cost',
|
|
||||||
value: objectData?.cost,
|
|
||||||
required: true,
|
|
||||||
type: 'currency'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'color',
|
|
||||||
label: 'Color',
|
|
||||||
value: objectData?.color,
|
|
||||||
required: true,
|
|
||||||
type: 'color'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'diameter',
|
|
||||||
label: 'Diameter',
|
|
||||||
value: objectData?.diameter,
|
|
||||||
required: true,
|
|
||||||
type: 'mm'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'density',
|
|
||||||
label: 'Density',
|
|
||||||
value: objectData?.density,
|
|
||||||
required: true,
|
|
||||||
type: 'density'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'url',
|
|
||||||
label: 'URL',
|
|
||||||
value: objectData?.url,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'barcode',
|
|
||||||
label: 'Barcode',
|
|
||||||
value: objectData?.barcode,
|
|
||||||
type: 'text'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
@ -221,7 +133,7 @@ const FilamentInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={filamentId} />
|
<NotesPanel _id={filamentId} />
|
||||||
</Card>
|
</Card>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { AuthContext } from '../context/AuthContext'
|
|||||||
import IdDisplay from '../common/IdDisplay'
|
import IdDisplay from '../common/IdDisplay'
|
||||||
import NewNoteType from './NoteTypes/NewNoteType'
|
import NewNoteType from './NoteTypes/NewNoteType'
|
||||||
import TimeDisplay from '../common/TimeDisplay'
|
import TimeDisplay from '../common/TimeDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import PlusIcon from '../../Icons/PlusIcon'
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
@ -304,7 +304,7 @@ const NoteTypes = () => {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/notetypes`}
|
url={`${config.backendUrl}/notetypes`}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { DownloadOutlined } from '@ant-design/icons'
|
|||||||
|
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import IdDisplay from '../common/IdDisplay'
|
import IdDisplay from '../common/IdDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import NewProduct from './Products/NewProduct'
|
import NewProduct from './Products/NewProduct'
|
||||||
import PartIcon from '../../Icons/PartIcon'
|
import PartIcon from '../../Icons/PartIcon'
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
@ -310,7 +310,7 @@ const Parts = () => {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/parts`}
|
url={`${config.backendUrl}/parts`}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import CheckIcon from '../../../Icons/CheckIcon.jsx'
|
|||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
import TimeDisplay from '../../common/TimeDisplay.jsx'
|
import TimeDisplay from '../../common/TimeDisplay.jsx'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
|
|
||||||
import config from '../../../../config.js'
|
import config from '../../../../config.js'
|
||||||
import BoolDisplay from '../../common/BoolDisplay.jsx'
|
import BoolDisplay from '../../common/BoolDisplay.jsx'
|
||||||
@ -664,7 +664,7 @@ const PartInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={partId} />
|
<NotesPanel _id={partId} />
|
||||||
</Card>
|
</Card>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { DownloadOutlined } from '@ant-design/icons'
|
|||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import IdDisplay from '../common/IdDisplay'
|
import IdDisplay from '../common/IdDisplay'
|
||||||
import TimeDisplay from '../common/TimeDisplay'
|
import TimeDisplay from '../common/TimeDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import NewProduct from './Products/NewProduct'
|
import NewProduct from './Products/NewProduct'
|
||||||
import ProductIcon from '../../Icons/ProductIcon'
|
import ProductIcon from '../../Icons/ProductIcon'
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
@ -353,7 +353,7 @@ const Products = () => {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/products`}
|
url={`${config.backendUrl}/products`}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Space, Button, Flex, Dropdown, Card } from 'antd'
|
|||||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
import InfoCollapse from '../../common/InfoCollapse'
|
import InfoCollapse from '../../common/InfoCollapse'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import ViewButton from '../../common/ViewButton'
|
import ViewButton from '../../common/ViewButton'
|
||||||
@ -206,7 +206,7 @@ const ProductInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={productId} />
|
<NotesPanel _id={productId} />
|
||||||
</Card>
|
</Card>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { ExportOutlined } from '@ant-design/icons'
|
|||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import IdDisplay from '../common/IdDisplay'
|
import IdDisplay from '../common/IdDisplay'
|
||||||
import TimeDisplay from '../common/TimeDisplay'
|
import TimeDisplay from '../common/TimeDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import PersonIcon from '../../Icons/PersonIcon'
|
import PersonIcon from '../../Icons/PersonIcon'
|
||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
@ -367,7 +367,7 @@ const Users = () => {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/users`}
|
url={`${config.backendUrl}/users`}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { LoadingOutlined } from '@ant-design/icons'
|
|||||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
import InfoCollapse from '../../common/InfoCollapse'
|
import InfoCollapse from '../../common/InfoCollapse'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import ViewButton from '../../common/ViewButton'
|
import ViewButton from '../../common/ViewButton'
|
||||||
@ -178,7 +178,7 @@ const UserInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={userId} />
|
<NotesPanel _id={userId} />
|
||||||
</Card>
|
</Card>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import IdDisplay from '../common/IdDisplay'
|
|||||||
import NewVendor from './Vendors/NewVendor'
|
import NewVendor from './Vendors/NewVendor'
|
||||||
import CountryDisplay from '../common/CountryDisplay'
|
import CountryDisplay from '../common/CountryDisplay'
|
||||||
import TimeDisplay from '../common/TimeDisplay'
|
import TimeDisplay from '../common/TimeDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import VendorIcon from '../../Icons/VendorIcon'
|
import VendorIcon from '../../Icons/VendorIcon'
|
||||||
import PlusIcon from '../../Icons/PlusIcon'
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
@ -363,7 +363,7 @@ const Vendors = () => {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/vendors`}
|
url={`${config.backendUrl}/vendors`}
|
||||||
|
|||||||
@ -7,17 +7,20 @@ import config from '../../../../config'
|
|||||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
import InfoCollapse from '../../common/InfoCollapse'
|
import InfoCollapse from '../../common/InfoCollapse'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import ViewButton from '../../common/ViewButton'
|
import ViewButton from '../../common/ViewButton'
|
||||||
|
|
||||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
import EditObjectForm from '../../common/EditObjectForm'
|
import EditObjectForm from '../../common/EditObjectForm'
|
||||||
import EditButtons from '../../common/EditButtons'
|
import EditButtons from '../../common/EditButtons'
|
||||||
import LockIndicator from '../Filaments/LockIndicator'
|
import LockIndicator from '../Filaments/LockIndicator'
|
||||||
|
import {
|
||||||
|
getModelProperties,
|
||||||
|
getPropertyValue
|
||||||
|
} from '../../../../database/ObjectModels'
|
||||||
|
|
||||||
const log = loglevel.getLogger('VendorInfo')
|
const log = loglevel.getLogger('VendorInfo')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
@ -115,67 +118,10 @@ const VendorInfo = () => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
items={[
|
items={getModelProperties('vendor').map((prop) => ({
|
||||||
{
|
...prop,
|
||||||
name: 'id',
|
value: getPropertyValue(objectData, prop.name)
|
||||||
label: 'ID',
|
}))}
|
||||||
value: objectData?._id,
|
|
||||||
type: 'id',
|
|
||||||
objectType: 'vendor',
|
|
||||||
showCopy: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'createdAt',
|
|
||||||
label: 'Created At',
|
|
||||||
value: objectData?.createdAt,
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
value: objectData?.name,
|
|
||||||
required: true,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'updatedAt',
|
|
||||||
label: 'Updated At',
|
|
||||||
value: objectData?.updatedAt,
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'website',
|
|
||||||
label: 'Website',
|
|
||||||
value: objectData?.website,
|
|
||||||
type: 'url'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'country',
|
|
||||||
label: 'Country',
|
|
||||||
value: objectData?.country,
|
|
||||||
type: 'country'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'contact',
|
|
||||||
label: 'Contact',
|
|
||||||
value: objectData?.contact,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'phone',
|
|
||||||
label: 'Phone',
|
|
||||||
value: objectData?.phone,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'email',
|
|
||||||
label: 'Email',
|
|
||||||
value: objectData?.email,
|
|
||||||
type: 'email'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
@ -187,7 +133,7 @@ const VendorInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={vendorId} />
|
<NotesPanel _id={vendorId} />
|
||||||
</Card>
|
</Card>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import ReloadIcon from '../../Icons/ReloadIcon'
|
|||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import CheckIcon from '../../Icons/CheckIcon'
|
import CheckIcon from '../../Icons/CheckIcon'
|
||||||
import TimeDisplay from '../common/TimeDisplay'
|
import TimeDisplay from '../common/TimeDisplay'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
import ListIcon from '../../Icons/ListIcon'
|
import ListIcon from '../../Icons/ListIcon'
|
||||||
import GridIcon from '../../Icons/GridIcon'
|
import GridIcon from '../../Icons/GridIcon'
|
||||||
import useViewMode from '../hooks/useViewMode'
|
import useViewMode from '../hooks/useViewMode'
|
||||||
@ -381,7 +381,7 @@ const GCodeFiles = () => {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/gcodefiles`}
|
url={`${config.backendUrl}/gcodefiles`}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { LoadingOutlined } from '@ant-design/icons'
|
|||||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
import InfoCollapse from '../../common/InfoCollapse'
|
import InfoCollapse from '../../common/InfoCollapse'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import ViewButton from '../../common/ViewButton'
|
import ViewButton from '../../common/ViewButton'
|
||||||
@ -16,6 +16,10 @@ import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
|||||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
import GCodeFileIcon from '../../../Icons/GCodeFileIcon.jsx'
|
import GCodeFileIcon from '../../../Icons/GCodeFileIcon.jsx'
|
||||||
|
import {
|
||||||
|
getModelProperties,
|
||||||
|
getPropertyValue
|
||||||
|
} from '../../../../database/ObjectModels.js'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@ -117,120 +121,10 @@ const GCodeFileInfo = () => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
items={[
|
items={getModelProperties('gcodeFile').map((prop) => ({
|
||||||
{
|
...prop,
|
||||||
name: '_id',
|
value: getPropertyValue(objectData, prop.name)
|
||||||
label: 'ID',
|
}))}
|
||||||
type: 'id',
|
|
||||||
objectType: 'gcodefile',
|
|
||||||
value: objectData?._id,
|
|
||||||
showCopy: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'createdAt',
|
|
||||||
label: 'Created At',
|
|
||||||
type: 'dateTime',
|
|
||||||
value: objectData?.createdAt,
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
type: 'text',
|
|
||||||
value: objectData?.name,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'updatedAt',
|
|
||||||
label: 'Updated At',
|
|
||||||
type: 'dateTime',
|
|
||||||
value: objectData?.updatedAt,
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'filament',
|
|
||||||
label: 'Filament',
|
|
||||||
type: 'object',
|
|
||||||
value: objectData?.filament,
|
|
||||||
objectType: 'filament',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cost',
|
|
||||||
label: 'Cost',
|
|
||||||
type: 'currency',
|
|
||||||
value: objectData?.cost,
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: [
|
|
||||||
'gcodeFileInfo',
|
|
||||||
'estimatedPrintingTimeNormalMode'
|
|
||||||
],
|
|
||||||
label: 'Est Print Time',
|
|
||||||
value:
|
|
||||||
objectData?.gcodeFileInfo
|
|
||||||
?.estimatedPrintingTimeNormalMode,
|
|
||||||
type: 'text',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['gcodeFileInfo', 'sparseInfillDensity'],
|
|
||||||
label: 'Infill Density',
|
|
||||||
value: objectData?.gcodeFileInfo?.sparseInfillDensity,
|
|
||||||
type: 'number',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['gcodeFileInfo', 'sparseInfillPattern'],
|
|
||||||
label: 'Infill Pattern',
|
|
||||||
value: objectData?.gcodeFileInfo?.sparseInfillPattern,
|
|
||||||
type: 'text',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['gcodeFileInfo', 'filamentUsedMm'],
|
|
||||||
label: 'Filament Used (mm)',
|
|
||||||
value: objectData?.gcodeFileInfo?.filamentUsedMm,
|
|
||||||
type: 'mm',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['gcodeFileInfo', 'filamentUsedG'],
|
|
||||||
label: 'Filament Used (g)',
|
|
||||||
value: objectData?.gcodeFileInfo?.filamentUsedG,
|
|
||||||
type: 'weight',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['gcodeFileInfo', 'nozzleTemperature'],
|
|
||||||
label: 'Hotend Temperature',
|
|
||||||
value: objectData?.gcodeFileInfo?.nozzleTemperature,
|
|
||||||
type: 'number',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['gcodeFileInfo', 'hotPlateTemp'],
|
|
||||||
label: 'Bed Temperature',
|
|
||||||
value: objectData?.gcodeFileInfo?.hotPlateTemp,
|
|
||||||
type: 'number',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['gcodeFileInfo', 'filamentSettingsId'],
|
|
||||||
label: 'Filament Profile',
|
|
||||||
value: objectData?.gcodeFileInfo?.filamentSettingsId,
|
|
||||||
type: 'text',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['gcodeFileInfo', 'printSettingsId'],
|
|
||||||
label: 'Print Profile',
|
|
||||||
value: objectData?.gcodeFileInfo?.printSettingsId,
|
|
||||||
type: 'text',
|
|
||||||
readOnly: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
type='gcodefile'
|
type='gcodefile'
|
||||||
/>
|
/>
|
||||||
@ -266,7 +160,7 @@ const GCodeFileInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={gcodeFileId} />
|
<NotesPanel _id={gcodeFileId} />
|
||||||
</Card>
|
</Card>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import CheckCircleIcon from '../../Icons/CheckCircleIcon.jsx'
|
|||||||
import PauseCircleIcon from '../../Icons/PauseCircleIcon.jsx'
|
import PauseCircleIcon from '../../Icons/PauseCircleIcon.jsx'
|
||||||
import XMarkCircleIcon from '../../Icons/XMarkCircleIcon.jsx'
|
import XMarkCircleIcon from '../../Icons/XMarkCircleIcon.jsx'
|
||||||
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon.jsx'
|
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon.jsx'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable.jsx'
|
||||||
import ListIcon from '../../Icons/ListIcon.jsx'
|
import ListIcon from '../../Icons/ListIcon.jsx'
|
||||||
import GridIcon from '../../Icons/GridIcon.jsx'
|
import GridIcon from '../../Icons/GridIcon.jsx'
|
||||||
import useViewMode from '../hooks/useViewMode.js'
|
import useViewMode from '../hooks/useViewMode.js'
|
||||||
@ -146,9 +146,10 @@ const Jobs = () => {
|
|||||||
{
|
{
|
||||||
title: 'State',
|
title: 'State',
|
||||||
key: 'state',
|
key: 'state',
|
||||||
|
dataIndex: 'state',
|
||||||
width: 240,
|
width: 240,
|
||||||
render: (record) => {
|
render: (state) => {
|
||||||
return <JobState job={record} showQuantity={false} showId={false} />
|
return <JobState state={state} showQuantity={false} showId={false} />
|
||||||
},
|
},
|
||||||
filterDropdown: ({
|
filterDropdown: ({
|
||||||
setSelectedKeys,
|
setSelectedKeys,
|
||||||
@ -393,7 +394,7 @@ const Jobs = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/jobs`}
|
url={`${config.backendUrl}/jobs`}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Space, Button, Flex, Dropdown, Card } from 'antd'
|
|||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
import InfoCollapse from '../../common/InfoCollapse'
|
import InfoCollapse from '../../common/InfoCollapse'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import ViewButton from '../../common/ViewButton'
|
import ViewButton from '../../common/ViewButton'
|
||||||
@ -17,6 +17,10 @@ import JobIcon from '../../../Icons/JobIcon'
|
|||||||
import AuditLogIcon from '../../../Icons/AuditLogIcon'
|
import AuditLogIcon from '../../../Icons/AuditLogIcon'
|
||||||
import NoteIcon from '../../../Icons/NoteIcon'
|
import NoteIcon from '../../../Icons/NoteIcon'
|
||||||
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
|
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
|
||||||
|
import {
|
||||||
|
getModelProperties,
|
||||||
|
getPropertyValue
|
||||||
|
} from '../../../../database/ObjectModels.js'
|
||||||
|
|
||||||
const JobInfo = () => {
|
const JobInfo = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
@ -114,72 +118,10 @@ const JobInfo = () => {
|
|||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
type='job'
|
type='job'
|
||||||
items={[
|
items={getModelProperties('job').map((prop) => ({
|
||||||
{
|
...prop,
|
||||||
name: '_id',
|
value: getPropertyValue(objectData, prop.name)
|
||||||
label: 'ID',
|
}))}
|
||||||
value: objectData?._id,
|
|
||||||
type: 'id',
|
|
||||||
objectType: 'job',
|
|
||||||
showCopy: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'state',
|
|
||||||
label: 'Status',
|
|
||||||
value: objectData,
|
|
||||||
type: 'state',
|
|
||||||
objectType: 'job',
|
|
||||||
showStatus: true,
|
|
||||||
showProgress: true,
|
|
||||||
showId: false,
|
|
||||||
showQuantity: false,
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gcodeFile',
|
|
||||||
label: 'GCode File',
|
|
||||||
value: objectData?.gcodeFile,
|
|
||||||
type: 'object',
|
|
||||||
objectType: 'gcodeFile',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gcodeFileId',
|
|
||||||
label: 'GCode File ID',
|
|
||||||
value: objectData?.gcodeFile?._id,
|
|
||||||
type: 'id',
|
|
||||||
objectType: 'gcodefile',
|
|
||||||
showHyperlink: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'quantity',
|
|
||||||
label: 'Quantity',
|
|
||||||
value: objectData?.quantity,
|
|
||||||
type: 'number',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'createdAt',
|
|
||||||
label: 'Created At',
|
|
||||||
value: objectData?.createdAt,
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'startedAt',
|
|
||||||
label: 'Started At',
|
|
||||||
value: objectData?.startedAt,
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'assignedPrinters',
|
|
||||||
label: 'Assigned Printers',
|
|
||||||
value: objectData?.printers?.length,
|
|
||||||
type: 'number',
|
|
||||||
readOnly: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
@ -203,7 +145,7 @@ const JobInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={jobId} />
|
<NotesPanel _id={jobId} />
|
||||||
</Card>
|
</Card>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import PlusIcon from '../../Icons/PlusIcon'
|
|||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import CheckIcon from '../../Icons/CheckIcon'
|
import CheckIcon from '../../Icons/CheckIcon'
|
||||||
import DashboardTable from '../common/DashboardTable'
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
import GridIcon from '../../Icons/GridIcon'
|
import GridIcon from '../../Icons/GridIcon'
|
||||||
@ -83,15 +83,12 @@ const Printers = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'State',
|
title: 'State',
|
||||||
|
dataIndex: 'state',
|
||||||
key: 'state',
|
key: 'state',
|
||||||
width: 240,
|
width: 240,
|
||||||
render: (record) => {
|
render: (state) => {
|
||||||
return (
|
return (
|
||||||
<PrinterState
|
<PrinterState state={state} showName={false} showControls={false} />
|
||||||
printer={record}
|
|
||||||
showName={false}
|
|
||||||
showControls={false}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -312,7 +309,7 @@ const Printers = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DashboardTable
|
<ObjectTable
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
columns={visibleColumns}
|
columns={visibleColumns}
|
||||||
url={`${config.backendUrl}/printers`}
|
url={`${config.backendUrl}/printers`}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Space, Button, Flex, Dropdown, Card } from 'antd'
|
|||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
import AuditLogTable from '../../common/AuditLogTable'
|
import AuditLogTable from '../../common/AuditLogTable'
|
||||||
import DashboardNotes from '../../common/DashboardNotes'
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
import InfoCollapse from '../../common/InfoCollapse'
|
import InfoCollapse from '../../common/InfoCollapse'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import ViewButton from '../../common/ViewButton'
|
import ViewButton from '../../common/ViewButton'
|
||||||
@ -16,6 +16,10 @@ import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
|||||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
|
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
|
||||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
|
import {
|
||||||
|
getModelProperties,
|
||||||
|
getPropertyValue
|
||||||
|
} from '../../../../database/ObjectModels.js'
|
||||||
|
|
||||||
const PrinterInfo = () => {
|
const PrinterInfo = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
@ -113,102 +117,10 @@ const PrinterInfo = () => {
|
|||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
type='printer'
|
type='printer'
|
||||||
items={[
|
items={getModelProperties('printer').map((prop) => ({
|
||||||
{
|
...prop,
|
||||||
name: '_id',
|
value: getPropertyValue(objectData, prop.name)
|
||||||
label: 'ID',
|
}))}
|
||||||
value: objectData?._id,
|
|
||||||
type: 'id',
|
|
||||||
objectType: 'printer',
|
|
||||||
showCopy: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'connectedAt',
|
|
||||||
label: 'Connected At',
|
|
||||||
value: objectData?.connectedAt,
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
value: objectData?.name,
|
|
||||||
required: true,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'state',
|
|
||||||
label: 'Status',
|
|
||||||
value: objectData,
|
|
||||||
type: 'state',
|
|
||||||
objectType: 'printer',
|
|
||||||
showName: false,
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vendor',
|
|
||||||
label: 'Vendor',
|
|
||||||
value: objectData?.vendor,
|
|
||||||
type: 'object',
|
|
||||||
objectType: 'vendor',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['moonraker', 'host'],
|
|
||||||
label: 'Host',
|
|
||||||
value: objectData?.moonraker?.host,
|
|
||||||
type: 'text',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vendorId',
|
|
||||||
label: 'Vendor ID',
|
|
||||||
value: objectData?.vendor?.id,
|
|
||||||
type: 'id',
|
|
||||||
objectType: 'vendor',
|
|
||||||
showHyperlink: true,
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: ['moonraker', 'port'],
|
|
||||||
label: 'Port',
|
|
||||||
value: objectData?.moonraker?.port,
|
|
||||||
type: 'number',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['moonraker', 'apiKey'],
|
|
||||||
label: 'API Key',
|
|
||||||
value: objectData?.moonraker?.apiKey,
|
|
||||||
type: 'secret',
|
|
||||||
reveal: true,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ['moonraker', 'protocol'],
|
|
||||||
label: 'Protocol',
|
|
||||||
value: objectData?.moonraker?.protocol,
|
|
||||||
type: 'wsprotocol',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'tags',
|
|
||||||
label: 'Tags',
|
|
||||||
value: objectData?.tags,
|
|
||||||
type: 'tags',
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'firmware',
|
|
||||||
label: 'Firmware Version',
|
|
||||||
value: objectData?.firmware,
|
|
||||||
type: 'text',
|
|
||||||
required: false,
|
|
||||||
readOnly: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
@ -233,7 +145,7 @@ const PrinterInfo = () => {
|
|||||||
key='notes'
|
key='notes'
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<DashboardNotes _id={printerId} />
|
<NotesPanel _id={printerId} />
|
||||||
</Card>
|
</Card>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
|
|||||||
@ -68,7 +68,10 @@ const formatValue = (value, propertyName) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AuditLogTable = forwardRef(
|
const AuditLogTable = forwardRef(
|
||||||
({ items, loading, showTargetColumn, showOwnerColumn }, ref) => {
|
(
|
||||||
|
{ items, loading = false, showTargetColumn = true, showOwnerColumn = true },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const [sortedInfo, setSortedInfo] = useState({
|
const [sortedInfo, setSortedInfo] = useState({
|
||||||
columnKey: 'createdAt',
|
columnKey: 'createdAt',
|
||||||
order: 'descend'
|
order: 'descend'
|
||||||
@ -207,10 +210,4 @@ AuditLogTable.propTypes = {
|
|||||||
showOwnerColumn: PropTypes.bool
|
showOwnerColumn: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditLogTable.defaultProps = {
|
|
||||||
loading: false,
|
|
||||||
showTargetColumn: true,
|
|
||||||
showOwnerColumn: true
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AuditLogTable
|
export default AuditLogTable
|
||||||
|
|||||||
@ -47,7 +47,7 @@ const ColorSelector = ({ value, onChange, disabled, required = false }) => {
|
|||||||
|
|
||||||
ColorSelector.propTypes = {
|
ColorSelector.propTypes = {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
required: PropTypes.bool
|
required: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,126 +1,26 @@
|
|||||||
import { TreeSelect } from 'antd'
|
import React from 'react'
|
||||||
import React, { useEffect, useState, useCallback } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import axios from 'axios'
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
|
import ObjectSelect from './ObjectSelect'
|
||||||
|
|
||||||
import FilamentStockDisplay from './FilamentStockDisplay'
|
const FilamentStockSelect = ({
|
||||||
|
onChange,
|
||||||
const FilamentStockSelect = ({ onChange, filter, useFilter, value }) => {
|
filter = {},
|
||||||
const [filamentStocksTreeData, setFilamentStocksTreeData] = useState([])
|
useFilter = false,
|
||||||
const [filamentStocksData, setFilamentStocksData] = useState([])
|
value,
|
||||||
const [loading, setLoading] = useState(false)
|
disabled = false
|
||||||
const [defaultValue, setDefaultValue] = useState(value)
|
}) => {
|
||||||
|
|
||||||
const getFilamentStockTitle = (filamentStock) => {
|
|
||||||
return (
|
return (
|
||||||
<FilamentStockDisplay filamentStock={filamentStock} showCopy={false} />
|
<ObjectSelect
|
||||||
)
|
endpoint={`${config.backendUrl}/filamentstocks`}
|
||||||
}
|
propertyOrder={['tags']}
|
||||||
|
filter={filter}
|
||||||
const fetchFilamentStocksData = async (property, filter) => {
|
useFilter={useFilter}
|
||||||
setLoading(true)
|
value={value}
|
||||||
try {
|
onChange={onChange}
|
||||||
const response = await axios.get(`${config.backendUrl}/filamentstocks`, {
|
disabled={disabled}
|
||||||
params: {
|
|
||||||
...filter,
|
|
||||||
property
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json'
|
|
||||||
},
|
|
||||||
withCredentials: true
|
|
||||||
})
|
|
||||||
setLoading(false)
|
|
||||||
return response.data
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateFilamentStockTreeNodes = useCallback(
|
|
||||||
async (node = null, filter = null) => {
|
|
||||||
if (!node) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const filamentStockData = await fetchFilamentStocksData(null, filter)
|
|
||||||
setFilamentStocksData(filamentStockData)
|
|
||||||
|
|
||||||
for (const filamentStock of filamentStockData) {
|
|
||||||
const newNode = {
|
|
||||||
id: filamentStock._id,
|
|
||||||
pId: node.id,
|
|
||||||
value: filamentStock._id,
|
|
||||||
key: filamentStock._id,
|
|
||||||
title: getFilamentStockTitle(filamentStock),
|
|
||||||
isLeaf: true
|
|
||||||
}
|
|
||||||
|
|
||||||
setFilamentStocksTreeData((prev) => {
|
|
||||||
const filtered = prev.filter((node) => node.id !== newNode.id)
|
|
||||||
return [...filtered, newNode]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleFilamentStocksTreeLoad = useCallback(
|
|
||||||
async (node) => {
|
|
||||||
if (node) {
|
|
||||||
await generateFilamentStockTreeNodes(node)
|
|
||||||
} else {
|
|
||||||
await generateFilamentStockTreeNodes({ id: 0 })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[generateFilamentStockTreeNodes]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleOnChange = (value, selectedOptions) => {
|
|
||||||
const filamentStockObject = filamentStocksData.filter(
|
|
||||||
(filamentStock) => filamentStock._id === value
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
onChange(filamentStockObject, selectedOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (value?._id != null) {
|
|
||||||
setDefaultValue(value)
|
|
||||||
}
|
|
||||||
}, [value])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (defaultValue != undefined) {
|
|
||||||
const newNode = {
|
|
||||||
id: defaultValue._id,
|
|
||||||
pId: 0,
|
|
||||||
value: defaultValue._id,
|
|
||||||
key: defaultValue._id,
|
|
||||||
title: getFilamentStockTitle(defaultValue),
|
|
||||||
isLeaf: true
|
|
||||||
}
|
|
||||||
setFilamentStocksTreeData([newNode])
|
|
||||||
} else {
|
|
||||||
setFilamentStocksTreeData([])
|
|
||||||
}
|
|
||||||
if (useFilter === true) {
|
|
||||||
generateFilamentStockTreeNodes({ id: 0 }, filter)
|
|
||||||
} else {
|
|
||||||
handleFilamentStocksTreeLoad(null)
|
|
||||||
}
|
|
||||||
}, [useFilter, defaultValue, filter])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TreeSelect
|
|
||||||
treeDataSimpleMode
|
|
||||||
value={defaultValue?._id}
|
|
||||||
loadData={handleFilamentStocksTreeLoad}
|
|
||||||
treeData={filamentStocksTreeData}
|
|
||||||
onChange={handleOnChange}
|
|
||||||
loading={loading}
|
|
||||||
placeholder='Select a filament stock'
|
placeholder='Select a filament stock'
|
||||||
|
type='filamentstock'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -129,12 +29,8 @@ FilamentStockSelect.propTypes = {
|
|||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
value: PropTypes.object,
|
value: PropTypes.object,
|
||||||
filter: PropTypes.object,
|
filter: PropTypes.object,
|
||||||
useFilter: PropTypes.bool
|
useFilter: PropTypes.bool,
|
||||||
}
|
disabled: PropTypes.bool
|
||||||
|
|
||||||
FilamentStockSelect.defaultProps = {
|
|
||||||
filter: {},
|
|
||||||
useFilter: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilamentStockSelect
|
export default FilamentStockSelect
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom'
|
|||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
import CopyButton from './CopyButton'
|
import CopyButton from './CopyButton'
|
||||||
import SpotlightTooltip from './SpotlightTooltip'
|
import SpotlightTooltip from './SpotlightTooltip'
|
||||||
import { getTypeMeta } from '../utils/Utils'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
|
||||||
const { Text, Link } = Typography
|
const { Text, Link } = Typography
|
||||||
|
|
||||||
@ -21,10 +21,10 @@ const IdDisplay = ({
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
|
|
||||||
const meta = getTypeMeta(type)
|
const model = getModelByName(type)
|
||||||
const prefix = meta.prefix
|
const prefix = model.prefix
|
||||||
const hyperlink = meta.url(id)
|
const hyperlink = model.url(id)
|
||||||
const IconComponent = meta.icon
|
const IconComponent = model.icon
|
||||||
const icon = <IconComponent style={{ paddingTop: '4px' }} />
|
const icon = <IconComponent style={{ paddingTop: '4px' }} />
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
|||||||
@ -1,47 +1,22 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Progress, Flex, Typography, Space } from 'antd'
|
import { Progress, Flex, Space } from 'antd'
|
||||||
import React, { useState, useContext, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { PrintServerContext } from '../context/PrintServerContext'
|
|
||||||
import IdDisplay from './IdDisplay'
|
|
||||||
import StateTag from './StateTag'
|
import StateTag from './StateTag'
|
||||||
|
|
||||||
const JobState = ({
|
const JobState = ({ state, showProgress = true, showState = true }) => {
|
||||||
job,
|
|
||||||
showProgress = true,
|
|
||||||
showStatus = true,
|
|
||||||
showId = true,
|
|
||||||
showQuantity = true
|
|
||||||
}) => {
|
|
||||||
const { printServer } = useContext(PrintServerContext)
|
|
||||||
const [currentState, setCurrentState] = useState(
|
const [currentState, setCurrentState] = useState(
|
||||||
job?.state || { type: 'unknown', progress: 0 }
|
state || { type: 'unknown', progress: 0 }
|
||||||
)
|
)
|
||||||
const [initialized, setInitialized] = useState(false)
|
|
||||||
const { Text } = Typography
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (printServer && !initialized && job?._id) {
|
if (state) {
|
||||||
setInitialized(true)
|
setCurrentState(state)
|
||||||
printServer.on('notify_job_update', (statusUpdate) => {
|
|
||||||
if (statusUpdate?._id === job._id && statusUpdate?.state) {
|
|
||||||
setCurrentState(statusUpdate.state)
|
|
||||||
}
|
}
|
||||||
})
|
}, [state])
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (printServer && initialized) {
|
|
||||||
printServer.off('notify_job_update')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [printServer, initialized, job?._id])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap='small' align={'center'}>
|
<Flex gap='small' align={'center'}>
|
||||||
{showId && (
|
{showState && (
|
||||||
<IdDisplay id={job._id} showCopy={false} type='job' longId={false} />
|
|
||||||
)}
|
|
||||||
{showQuantity && <Text>({job.quantity})</Text>}
|
|
||||||
{showStatus && (
|
|
||||||
<Space>
|
<Space>
|
||||||
<StateTag state={currentState?.type} />
|
<StateTag state={currentState?.type} />
|
||||||
</Space>
|
</Space>
|
||||||
@ -60,18 +35,9 @@ const JobState = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
JobState.propTypes = {
|
JobState.propTypes = {
|
||||||
job: PropTypes.shape({
|
state: PropTypes.object,
|
||||||
_id: PropTypes.string,
|
|
||||||
quantity: PropTypes.number,
|
|
||||||
state: PropTypes.shape({
|
|
||||||
type: PropTypes.string,
|
|
||||||
progress: PropTypes.number
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
showProgress: PropTypes.bool,
|
showProgress: PropTypes.bool,
|
||||||
showQuantity: PropTypes.bool,
|
showState: PropTypes.bool
|
||||||
showId: PropTypes.bool,
|
|
||||||
showStatus: PropTypes.bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default JobState
|
export default JobState
|
||||||
|
|||||||
@ -259,7 +259,7 @@ NoteItem.propTypes = {
|
|||||||
onChildNoteAdded: PropTypes.func
|
onChildNoteAdded: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
const DashboardNotes = ({ _id, onNewNote }) => {
|
const NotesPanel = ({ _id, onNewNote }) => {
|
||||||
const [newNoteOpen, setNewNoteOpen] = useState(false)
|
const [newNoteOpen, setNewNoteOpen] = useState(false)
|
||||||
const [showMarkdown, setShowMarkdown] = useState(false)
|
const [showMarkdown, setShowMarkdown] = useState(false)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
@ -686,9 +686,9 @@ const DashboardNotes = ({ _id, onNewNote }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
DashboardNotes.propTypes = {
|
NotesPanel.propTypes = {
|
||||||
_id: PropTypes.string.isRequired,
|
_id: PropTypes.string.isRequired,
|
||||||
onNewNote: PropTypes.func
|
onNewNote: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DashboardNotes
|
export default NotesPanel
|
||||||
@ -32,6 +32,7 @@ import ColorSelector from './ColorSelector'
|
|||||||
import SecretDisplay from './SecretDisplay'
|
import SecretDisplay from './SecretDisplay'
|
||||||
import EyeIcon from '../../Icons/EyeIcon'
|
import EyeIcon from '../../Icons/EyeIcon'
|
||||||
import EyeSlashIcon from '../../Icons/EyeSlashIcon'
|
import EyeSlashIcon from '../../Icons/EyeSlashIcon'
|
||||||
|
import FilamentStockState from './FilamentStockState'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@ -57,8 +58,14 @@ const ObjectProperty = ({
|
|||||||
readOnly = false,
|
readOnly = false,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
|
// Split the name by "." to handle nested object properties
|
||||||
|
var formItemName = name
|
||||||
|
|
||||||
|
if (name?.includes('.')) {
|
||||||
|
formItemName = name ? name.split('.') : undefined
|
||||||
|
}
|
||||||
|
|
||||||
const renderProperty = () => {
|
const renderProperty = () => {
|
||||||
console.log('Rendering')
|
|
||||||
if (!isEditing || readOnly) {
|
if (!isEditing || readOnly) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'secret':
|
case 'secret':
|
||||||
@ -120,7 +127,11 @@ const ObjectProperty = ({
|
|||||||
}
|
}
|
||||||
case 'number': {
|
case 'number': {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return <Text>{value.length}</Text>
|
||||||
|
} else {
|
||||||
return <Text>{value}</Text>
|
return <Text>{value}</Text>
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>n/a</Text>
|
||||||
}
|
}
|
||||||
@ -151,17 +162,18 @@ const ObjectProperty = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'state': {
|
case 'state': {
|
||||||
if (value && value?.state) {
|
if (value && value?.type) {
|
||||||
switch (objectType) {
|
switch (objectType) {
|
||||||
case 'printer':
|
case 'printer':
|
||||||
return <PrinterState printer={value} {...rest} />
|
return <PrinterState state={value} {...rest} />
|
||||||
case 'job':
|
case 'job':
|
||||||
return <JobState job={value} {...rest} />
|
return <JobState state={value} {...rest} />
|
||||||
case 'subjob':
|
case 'subJob':
|
||||||
return <SubJobState subJob={value} {...rest} />
|
return <SubJobState state={value} {...rest} />
|
||||||
|
case 'filamentStock':
|
||||||
|
return <FilamentStockState state={value} {...rest} />
|
||||||
default:
|
default:
|
||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>No Object Type Specified</Text>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>n/a</Text>
|
||||||
@ -251,7 +263,7 @@ const ObjectProperty = ({
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'secret':
|
case 'secret':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
placeholder={label}
|
placeholder={label}
|
||||||
{...mergedFormItemProps}
|
{...mergedFormItemProps}
|
||||||
@ -263,7 +275,7 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
case 'wsprotocol':
|
case 'wsprotocol':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<Select
|
<Select
|
||||||
defaultValue='ws'
|
defaultValue='ws'
|
||||||
options={[
|
options={[
|
||||||
@ -276,7 +288,7 @@ const ObjectProperty = ({
|
|||||||
case 'bool':
|
case 'bool':
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={name}
|
name={formItemName}
|
||||||
{...mergedFormItemProps}
|
{...mergedFormItemProps}
|
||||||
valuePropName='checked'
|
valuePropName='checked'
|
||||||
>
|
>
|
||||||
@ -286,7 +298,7 @@ const ObjectProperty = ({
|
|||||||
case 'dateTime':
|
case 'dateTime':
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={name}
|
name={formItemName}
|
||||||
{...mergedFormItemProps}
|
{...mergedFormItemProps}
|
||||||
getValueProps={(v) => ({ value: v ? dayjs(v) : null })}
|
getValueProps={(v) => ({ value: v ? dayjs(v) : null })}
|
||||||
>
|
>
|
||||||
@ -295,7 +307,7 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
case 'currency':
|
case 'currency':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
prefix='£'
|
prefix='£'
|
||||||
suffix='/kg'
|
suffix='/kg'
|
||||||
@ -306,14 +318,14 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
case 'country':
|
case 'country':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<CountrySelect />
|
<CountrySelect />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
case 'color':
|
case 'color':
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={name}
|
name={formItemName}
|
||||||
{...mergedFormItemProps}
|
{...mergedFormItemProps}
|
||||||
valuePropName='value'
|
valuePropName='value'
|
||||||
getValueFromEvent={(v) => v}
|
getValueFromEvent={(v) => v}
|
||||||
@ -323,7 +335,7 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
case 'weight':
|
case 'weight':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
suffix='g'
|
suffix='g'
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
@ -333,7 +345,7 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
case 'number':
|
case 'number':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
placeholder={label}
|
placeholder={label}
|
||||||
@ -343,13 +355,13 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
case 'text':
|
case 'text':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<Input placeholder={label} {...mergedFormItemProps} />
|
<Input placeholder={label} {...mergedFormItemProps} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
case 'material':
|
case 'material':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<Select options={MATERIAL_OPTIONS} placeholder={label} />
|
<Select options={MATERIAL_OPTIONS} placeholder={label} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
@ -364,31 +376,31 @@ const ObjectProperty = ({
|
|||||||
switch (objectType) {
|
switch (objectType) {
|
||||||
case 'vendor':
|
case 'vendor':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<VendorSelect placeholder={label} />
|
<VendorSelect placeholder={label} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
case 'printer':
|
case 'printer':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<PrinterSelect placeholder={label} />
|
<PrinterSelect placeholder={label} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
case 'gcodefile':
|
case 'gcodefile':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<GCodeFileSelect placeholder={label} />
|
<GCodeFileSelect placeholder={label} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
case 'filament':
|
case 'filament':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<FilamentSelect placeholder={label} />
|
<FilamentSelect placeholder={label} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
case 'part':
|
case 'part':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<PartSelect placeholder={label} />
|
<PartSelect placeholder={label} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
@ -398,7 +410,7 @@ const ObjectProperty = ({
|
|||||||
|
|
||||||
case 'density':
|
case 'density':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
suffix='g/cm³'
|
suffix='g/cm³'
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
@ -408,7 +420,7 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
case 'mm':
|
case 'mm':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
suffix='mm'
|
suffix='mm'
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
@ -418,13 +430,13 @@ const ObjectProperty = ({
|
|||||||
)
|
)
|
||||||
case 'tags':
|
case 'tags':
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<TagsInput />
|
<TagsInput />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<Form.Item name={name} {...mergedFormItemProps}>
|
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||||
<Input placeholder={label} {...mergedFormItemProps} />
|
<Input placeholder={label} {...mergedFormItemProps} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
@ -437,18 +449,7 @@ const ObjectProperty = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
ObjectProperty.propTypes = {
|
ObjectProperty.propTypes = {
|
||||||
type: PropTypes.oneOf([
|
type: PropTypes.string.isRequired,
|
||||||
'text',
|
|
||||||
'number',
|
|
||||||
'currency',
|
|
||||||
'color',
|
|
||||||
'weight',
|
|
||||||
'vendor',
|
|
||||||
'material',
|
|
||||||
'id',
|
|
||||||
'density',
|
|
||||||
'mm'
|
|
||||||
]),
|
|
||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
formItemProps: PropTypes.object,
|
formItemProps: PropTypes.object,
|
||||||
@ -456,10 +457,8 @@ ObjectProperty.propTypes = {
|
|||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
showLabel: PropTypes.bool,
|
showLabel: PropTypes.bool,
|
||||||
objectType: PropTypes.string.isRequired,
|
objectType: PropTypes.string,
|
||||||
readOnly: PropTypes.bool
|
readOnly: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectProperty.defaultProps = {}
|
|
||||||
|
|
||||||
export default ObjectProperty
|
export default ObjectProperty
|
||||||
|
|||||||
@ -2,12 +2,41 @@ import React, { useEffect, useState, useCallback } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { TreeSelect, Typography, Flex, Badge, Space, Button, Input } from 'antd'
|
import { TreeSelect, Typography, Flex, Badge, Space, Button, Input } from 'antd'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { getTypeMeta } from '../utils/Utils'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import IdDisplay from './IdDisplay'
|
import IdDisplay from './IdDisplay'
|
||||||
import CountryDisplay from './CountryDisplay'
|
|
||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
|
import ObjectProperty from './ObjectProperty'
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
const { SHOW_CHILD } = TreeSelect
|
const { SHOW_CHILD } = TreeSelect
|
||||||
|
|
||||||
|
// --- Utility: Resolve nested property path (e.g., 'filament.diameter') ---
|
||||||
|
function resolvePropertyPath(obj, path) {
|
||||||
|
if (!obj || !path) return { value: undefined, finalProperty: undefined }
|
||||||
|
const props = path.split('.')
|
||||||
|
let value = obj
|
||||||
|
for (const prop of props) {
|
||||||
|
if (value && typeof value === 'object') {
|
||||||
|
value = value[prop]
|
||||||
|
} else {
|
||||||
|
return { value: undefined, finalProperty: prop }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { value, finalProperty: props[props.length - 1] }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Utility: Build filter object for a node based on propertyOrder ---
|
||||||
|
function buildFilterForNode(node, treeData, propertyOrder) {
|
||||||
|
let filterObj = {}
|
||||||
|
let currentId = node.id
|
||||||
|
while (currentId !== 0) {
|
||||||
|
const currentNode = treeData.find((d) => d.id === currentId)
|
||||||
|
if (!currentNode) break
|
||||||
|
filterObj[propertyOrder[currentNode.propertyId]] = currentNode.value
|
||||||
|
currentId = currentNode.pId
|
||||||
|
}
|
||||||
|
return filterObj
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ObjectSelect - a generic, reusable async TreeSelect for hierarchical object selection.
|
* ObjectSelect - a generic, reusable async TreeSelect for hierarchical object selection.
|
||||||
*
|
*
|
||||||
@ -35,29 +64,14 @@ const ObjectSelect = ({
|
|||||||
type = 'unknown',
|
type = 'unknown',
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
|
// --- State ---
|
||||||
const [treeData, setTreeData] = useState([])
|
const [treeData, setTreeData] = useState([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [defaultValue, setDefaultValue] = useState(treeCheckable ? [] : value)
|
const [defaultValue, setDefaultValue] = useState(treeCheckable ? [] : value)
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
|
|
||||||
// Helper to get filter object for a node
|
// --- API: Fetch data for a property level or leaf ---
|
||||||
const getFilter = useCallback(
|
|
||||||
(node) => {
|
|
||||||
let filterObj = {}
|
|
||||||
let currentId = node.id
|
|
||||||
while (currentId !== 0) {
|
|
||||||
const currentNode = treeData.find((d) => d.id === currentId)
|
|
||||||
if (!currentNode) break
|
|
||||||
filterObj[propertyOrder[currentNode.propertyId]] = currentNode.value
|
|
||||||
currentId = currentNode.pId
|
|
||||||
}
|
|
||||||
return filterObj
|
|
||||||
},
|
|
||||||
[treeData, propertyOrder]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fetch data from API
|
|
||||||
const fetchData = useCallback(
|
const fetchData = useCallback(
|
||||||
async (property, filter, search) => {
|
async (property, filter, search) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@ -74,14 +88,13 @@ const ObjectSelect = ({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setError(true)
|
setError(true)
|
||||||
// Optionally handle error
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[endpoint]
|
[endpoint]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fetch single object by ID
|
// --- API: Fetch a single object by ID ---
|
||||||
const fetchObjectById = useCallback(
|
const fetchObjectById = useCallback(
|
||||||
async (objectId) => {
|
async (objectId) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@ -95,28 +108,26 @@ const ObjectSelect = ({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setError(true)
|
setError(true)
|
||||||
console.error('Failed to fetch object by ID:', err)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[endpoint]
|
[endpoint]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper to render the title for a node
|
// --- Render node title ---
|
||||||
const renderTitle = useCallback(
|
const renderTitle = useCallback(
|
||||||
(item, isLeaf) => {
|
(item) => {
|
||||||
if (!isLeaf) {
|
if (item.propertyType) {
|
||||||
// For category nodes, check if it's a country property
|
return (
|
||||||
const currentProperty = propertyOrder[item.propertyId]
|
<ObjectProperty
|
||||||
if (currentProperty === 'country' && item.value) {
|
type={item.propertyType}
|
||||||
return <CountryDisplay countryCode={item.value} />
|
value={item.value}
|
||||||
}
|
objectType={type}
|
||||||
// For other category nodes, just show the value
|
/>
|
||||||
return <Text>{item[propertyOrder[item.propertyId]] || item.value}</Text>
|
)
|
||||||
}
|
} else {
|
||||||
// For leaf nodes, show icon, name, and id
|
const model = getModelByName(type)
|
||||||
const meta = getTypeMeta(type)
|
const Icon = model.icon
|
||||||
const Icon = meta.icon
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={'small'} align='center' style={{ width: '100%' }}>
|
<Flex gap={'small'} align='center' style={{ width: '100%' }}>
|
||||||
{Icon && <Icon />}
|
{Icon && <Icon />}
|
||||||
@ -125,275 +136,106 @@ const ObjectSelect = ({
|
|||||||
<IdDisplay id={item._id} longId={false} type={type} />
|
<IdDisplay id={item._id} longId={false} type={type} />
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[propertyOrder, type]
|
[type]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build tree path for a default object
|
// --- Build tree nodes for a property level ---
|
||||||
const buildTreePathForObject = useCallback(
|
const buildCategoryNodes = useCallback(
|
||||||
async (object) => {
|
(data, propertyName, propertyId, parentId) => {
|
||||||
if (!object || !propertyOrder || propertyOrder.length === 0) return
|
return data.map((item) => {
|
||||||
|
let resolved = resolvePropertyPath(item, propertyName)
|
||||||
const newNodes = []
|
let value = resolved.value
|
||||||
let currentPId = 0
|
let propertyType = resolved.finalProperty
|
||||||
|
|
||||||
// Build category nodes for each property level and load all available options
|
|
||||||
for (let i = 0; i < propertyOrder.length; i++) {
|
|
||||||
const propertyName = propertyOrder[i]
|
|
||||||
let propertyValue
|
|
||||||
|
|
||||||
// Handle nested property access (e.g., 'filament.diameter')
|
|
||||||
if (propertyName.includes('.')) {
|
|
||||||
const propertyPath = propertyName.split('.')
|
|
||||||
let currentValue = object
|
|
||||||
for (const prop of propertyPath) {
|
|
||||||
if (currentValue && typeof currentValue === 'object') {
|
|
||||||
currentValue = currentValue[prop]
|
|
||||||
} else {
|
|
||||||
currentValue = undefined
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
propertyValue = currentValue
|
|
||||||
} else {
|
|
||||||
propertyValue = object[propertyName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build filter for this level
|
|
||||||
let filterObj = {}
|
|
||||||
for (let j = 0; j < i; j++) {
|
|
||||||
const prevPropertyName = propertyOrder[j]
|
|
||||||
let prevPropertyValue
|
|
||||||
|
|
||||||
if (prevPropertyName.includes('.')) {
|
|
||||||
const propertyPath = prevPropertyName.split('.')
|
|
||||||
let currentValue = object
|
|
||||||
for (const prop of propertyPath) {
|
|
||||||
if (currentValue && typeof currentValue === 'object') {
|
|
||||||
currentValue = currentValue[prop]
|
|
||||||
} else {
|
|
||||||
currentValue = undefined
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevPropertyValue = currentValue
|
|
||||||
} else {
|
|
||||||
prevPropertyValue = object[prevPropertyName]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevPropertyValue !== undefined && prevPropertyValue !== null) {
|
|
||||||
filterObj[prevPropertyName] = prevPropertyValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all available options for this property level
|
|
||||||
const data = await fetchData(propertyName, filterObj, '')
|
|
||||||
|
|
||||||
// Create nodes for all available options at this level
|
|
||||||
const levelNodes = data.map((item) => {
|
|
||||||
let value
|
|
||||||
if (typeof item === 'object' && item !== null) {
|
|
||||||
if (propertyName.includes('.')) {
|
|
||||||
const propertyPath = propertyName.split('.')
|
|
||||||
let currentValue = item
|
|
||||||
for (const prop of propertyPath) {
|
|
||||||
if (currentValue && typeof currentValue === 'object') {
|
|
||||||
currentValue = currentValue[prop]
|
|
||||||
} else {
|
|
||||||
currentValue = undefined
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value = currentValue
|
|
||||||
} else {
|
|
||||||
value = item[propertyName]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value = item
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: value,
|
id: value,
|
||||||
pId: currentPId,
|
pId: parentId,
|
||||||
value: value,
|
|
||||||
key: value,
|
|
||||||
propertyId: i,
|
|
||||||
title: renderTitle({ ...item, value }, false),
|
|
||||||
isLeaf: false,
|
|
||||||
selectable: false,
|
|
||||||
raw: item
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
newNodes.push(...levelNodes)
|
|
||||||
|
|
||||||
// Update currentPId to the object's property value for the next level
|
|
||||||
if (propertyValue !== undefined && propertyValue !== null) {
|
|
||||||
currentPId = propertyValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load all leaf nodes at the final level
|
|
||||||
let finalFilterObj = {}
|
|
||||||
for (let j = 0; j < propertyOrder.length - 1; j++) {
|
|
||||||
const prevPropertyName = propertyOrder[j]
|
|
||||||
let prevPropertyValue
|
|
||||||
|
|
||||||
if (prevPropertyName.includes('.')) {
|
|
||||||
const propertyPath = prevPropertyName.split('.')
|
|
||||||
let currentValue = object
|
|
||||||
for (const prop of propertyPath) {
|
|
||||||
if (currentValue && typeof currentValue === 'object') {
|
|
||||||
currentValue = currentValue[prop]
|
|
||||||
} else {
|
|
||||||
currentValue = undefined
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevPropertyValue = currentValue
|
|
||||||
} else {
|
|
||||||
prevPropertyValue = object[prevPropertyName]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevPropertyValue !== undefined && prevPropertyValue !== null) {
|
|
||||||
finalFilterObj[prevPropertyName] = prevPropertyValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const leafData = await fetchData(null, finalFilterObj, '')
|
|
||||||
const leafNodes = leafData.map((item) => ({
|
|
||||||
id: item._id || item.id || item.value,
|
|
||||||
pId: currentPId,
|
|
||||||
value: item._id || item.id || item.value,
|
|
||||||
key: item._id || item.id || item.value,
|
|
||||||
title: renderTitle(item, true),
|
|
||||||
isLeaf: true,
|
|
||||||
raw: item
|
|
||||||
}))
|
|
||||||
|
|
||||||
newNodes.push(...leafNodes)
|
|
||||||
|
|
||||||
setTreeData(newNodes)
|
|
||||||
setDefaultValue(object._id || object.id)
|
|
||||||
},
|
|
||||||
[propertyOrder, renderTitle, fetchData]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Generate leaf nodes
|
|
||||||
const generateLeafNodes = useCallback(
|
|
||||||
async (node = null, filterArg = null, search = '') => {
|
|
||||||
if (!node) return
|
|
||||||
const actualFilter = filterArg === null ? getFilter(node) : filterArg
|
|
||||||
const data = await fetchData(null, actualFilter, search)
|
|
||||||
const newNodes = data.map((item) => {
|
|
||||||
const isLeaf = true
|
|
||||||
return {
|
|
||||||
id: item._id || item.id || item.value,
|
|
||||||
pId: node.id,
|
|
||||||
value: item._id || item.id || item.value,
|
|
||||||
key: item._id || item.id || item.value,
|
|
||||||
title: renderTitle(item, isLeaf),
|
|
||||||
isLeaf: true,
|
|
||||||
raw: item
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setTreeData((prev) => [...prev, ...newNodes])
|
|
||||||
},
|
|
||||||
[fetchData, getFilter, renderTitle]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Generate category nodes
|
|
||||||
const generateCategoryNodes = useCallback(
|
|
||||||
async (node = null, search = '') => {
|
|
||||||
let filterObj = {}
|
|
||||||
let propertyId = 0
|
|
||||||
if (!node) {
|
|
||||||
node = { id: 0 }
|
|
||||||
} else {
|
|
||||||
filterObj = getFilter(node)
|
|
||||||
propertyId = node.propertyId + 1
|
|
||||||
}
|
|
||||||
const propertyName = propertyOrder[propertyId]
|
|
||||||
const data = await fetchData(propertyName, filterObj, search)
|
|
||||||
const newNodes = data.map((item) => {
|
|
||||||
const isLeaf = false
|
|
||||||
// Handle both cases: when item is a simple value or when it's an object
|
|
||||||
let value
|
|
||||||
if (typeof item === 'object' && item !== null) {
|
|
||||||
// Handle nested property access (e.g., 'filament.diameter')
|
|
||||||
if (propertyName.includes('.')) {
|
|
||||||
const propertyPath = propertyName.split('.')
|
|
||||||
let currentValue = item
|
|
||||||
for (const prop of propertyPath) {
|
|
||||||
if (currentValue && typeof currentValue === 'object') {
|
|
||||||
currentValue = currentValue[prop]
|
|
||||||
} else {
|
|
||||||
currentValue = undefined
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value = currentValue
|
|
||||||
} else {
|
|
||||||
// If item is an object, try to get the property value
|
|
||||||
value = item[propertyName]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If item is a simple value (string, number, etc.), use it directly
|
|
||||||
value = item
|
|
||||||
}
|
|
||||||
const title = renderTitle({ ...item, value }, isLeaf)
|
|
||||||
return {
|
|
||||||
id: value,
|
|
||||||
pId: node.id,
|
|
||||||
value: value,
|
value: value,
|
||||||
key: value,
|
key: value,
|
||||||
propertyId: propertyId,
|
propertyId: propertyId,
|
||||||
title: title,
|
title: renderTitle({ ...item, value, propertyType }),
|
||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
raw: item
|
raw: item
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setTreeData((prev) => [...prev, ...newNodes])
|
|
||||||
},
|
},
|
||||||
[fetchData, getFilter, propertyOrder, renderTitle]
|
[renderTitle]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tree loader
|
// --- Build tree nodes for leaf level ---
|
||||||
|
const buildLeafNodes = useCallback(
|
||||||
|
(data, parentId) => {
|
||||||
|
return data.map((item) => {
|
||||||
|
const value = item._id || item.id || item.value
|
||||||
|
return {
|
||||||
|
id: value,
|
||||||
|
pId: parentId,
|
||||||
|
value: value,
|
||||||
|
key: value,
|
||||||
|
title: renderTitle(item),
|
||||||
|
isLeaf: true,
|
||||||
|
raw: item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[renderTitle]
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Tree loader: load children for a node or root ---
|
||||||
const handleTreeLoad = useCallback(
|
const handleTreeLoad = useCallback(
|
||||||
async (node) => {
|
async (node) => {
|
||||||
|
if (!propertyOrder.length) return
|
||||||
if (node) {
|
if (node) {
|
||||||
|
// Not at leaf level yet
|
||||||
if (node.propertyId !== propertyOrder.length - 1) {
|
if (node.propertyId !== propertyOrder.length - 1) {
|
||||||
await generateCategoryNodes(node, searchValue)
|
const nextPropertyId = node.propertyId + 1
|
||||||
|
const propertyName = propertyOrder[nextPropertyId]
|
||||||
|
const filterObj = buildFilterForNode(node, treeData, propertyOrder)
|
||||||
|
const data = await fetchData(propertyName, filterObj, searchValue)
|
||||||
|
setTreeData((prev) => [
|
||||||
|
...prev,
|
||||||
|
...buildCategoryNodes(data, propertyName, nextPropertyId, node.id)
|
||||||
|
])
|
||||||
} else {
|
} else {
|
||||||
await generateLeafNodes(node, null, searchValue)
|
// At leaf level
|
||||||
|
const filterObj = buildFilterForNode(node, treeData, propertyOrder)
|
||||||
|
const data = await fetchData(null, filterObj, searchValue)
|
||||||
|
setTreeData((prev) => [...prev, ...buildLeafNodes(data, node.id)])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await generateCategoryNodes(null, searchValue)
|
// Root load
|
||||||
|
const propertyName = propertyOrder[0]
|
||||||
|
const data = await fetchData(propertyName, {}, searchValue)
|
||||||
|
setTreeData(buildCategoryNodes(data, propertyName, 0, 0))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[propertyOrder, generateCategoryNodes, generateLeafNodes, searchValue]
|
[
|
||||||
|
propertyOrder,
|
||||||
|
treeData,
|
||||||
|
fetchData,
|
||||||
|
buildCategoryNodes,
|
||||||
|
buildLeafNodes,
|
||||||
|
searchValue
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
// OnChange handler
|
// --- OnChange handler ---
|
||||||
const handleOnChange = (val, selectedOptions) => {
|
const handleOnChange = (val, selectedOptions) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
if (treeCheckable) {
|
if (treeCheckable) {
|
||||||
// Handle multiple selections with checkboxes
|
// Multi-select
|
||||||
const selectedObjects = []
|
let selectedObjects = []
|
||||||
if (Array.isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
val.forEach((selectedValue) => {
|
selectedObjects = val.map((selectedValue) => {
|
||||||
const node = treeData.find((n) => n.value === selectedValue)
|
const node = treeData.find((n) => n.value === selectedValue)
|
||||||
if (node) {
|
return node ? node.raw : selectedValue
|
||||||
selectedObjects.push(node.raw)
|
|
||||||
} else {
|
|
||||||
selectedObjects.push(selectedValue)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onChange(selectedObjects, selectedOptions)
|
onChange(selectedObjects, selectedOptions)
|
||||||
} else {
|
} else {
|
||||||
// Handle single selection
|
// Single select
|
||||||
const node = treeData.find((n) => n.value === val)
|
const node = treeData.find((n) => n.value === val)
|
||||||
onChange(node ? node.raw : val, selectedOptions)
|
onChange(node ? node.raw : val, selectedOptions)
|
||||||
}
|
}
|
||||||
@ -401,21 +243,18 @@ const ObjectSelect = ({
|
|||||||
setDefaultValue(val)
|
setDefaultValue(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search handler
|
// --- Search handler ---
|
||||||
const handleSearch = (val) => {
|
const handleSearch = (val) => {
|
||||||
setSearchValue(val)
|
setSearchValue(val)
|
||||||
setTreeData([])
|
setTreeData([])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep defaultValue in sync and handle object values
|
// --- Sync defaultValue and load tree path for object values ---
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (treeCheckable) {
|
if (treeCheckable) {
|
||||||
// Handle array of values for multi-select
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const valueIds = value.map((v) => v._id || v.id || v)
|
const valueIds = value.map((v) => v._id || v.id || v)
|
||||||
setDefaultValue(valueIds)
|
setDefaultValue(valueIds)
|
||||||
|
|
||||||
// Load tree paths for any objects that aren't already loaded
|
|
||||||
value.forEach((item) => {
|
value.forEach((item) => {
|
||||||
if (item && typeof item === 'object' && item._id) {
|
if (item && typeof item === 'object' && item._id) {
|
||||||
const existingNode = treeData.find(
|
const existingNode = treeData.find(
|
||||||
@ -424,7 +263,14 @@ const ObjectSelect = ({
|
|||||||
if (!existingNode) {
|
if (!existingNode) {
|
||||||
fetchObjectById(item._id).then((object) => {
|
fetchObjectById(item._id).then((object) => {
|
||||||
if (object) {
|
if (object) {
|
||||||
buildTreePathForObject(object)
|
// For multi-select, just add the leaf node
|
||||||
|
setTreeData((prev) => [
|
||||||
|
...prev,
|
||||||
|
...buildLeafNodes(
|
||||||
|
[object],
|
||||||
|
object[propertyOrder[propertyOrder.length - 2]] || 0
|
||||||
|
)
|
||||||
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -434,36 +280,44 @@ const ObjectSelect = ({
|
|||||||
setDefaultValue([])
|
setDefaultValue([])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle single value
|
|
||||||
if (value?._id) {
|
if (value?._id) {
|
||||||
setDefaultValue(value._id)
|
setDefaultValue(value._id)
|
||||||
}
|
|
||||||
|
|
||||||
// Check if value is an object with _id (default object case)
|
|
||||||
if (value && typeof value === 'object' && value._id) {
|
|
||||||
// If we already have this object loaded, don't fetch again
|
|
||||||
const existingNode = treeData.find((node) => node.value === value._id)
|
const existingNode = treeData.find((node) => node.value === value._id)
|
||||||
if (!existingNode) {
|
if (!existingNode) {
|
||||||
fetchObjectById(value._id).then((object) => {
|
fetchObjectById(value._id).then((object) => {
|
||||||
if (object) {
|
if (object) {
|
||||||
buildTreePathForObject(object)
|
setTreeData((prev) => [
|
||||||
|
...prev,
|
||||||
|
...buildLeafNodes(
|
||||||
|
[object],
|
||||||
|
object[propertyOrder[propertyOrder.length - 2]] || 0
|
||||||
|
)
|
||||||
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [value, treeData, fetchObjectById, buildTreePathForObject, treeCheckable])
|
}, [
|
||||||
|
value,
|
||||||
|
treeData,
|
||||||
|
fetchObjectById,
|
||||||
|
buildLeafNodes,
|
||||||
|
propertyOrder,
|
||||||
|
treeCheckable
|
||||||
|
])
|
||||||
|
|
||||||
// Initial load
|
// --- Initial load ---
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (treeData.length === 0 && !error && !loading) {
|
if (treeData.length === 0 && !error && !loading) {
|
||||||
// If we have a default object value, don't load the regular tree
|
|
||||||
if (!treeCheckable && value && typeof value === 'object' && value._id) {
|
if (!treeCheckable && value && typeof value === 'object' && value._id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useFilter || searchValue) {
|
if (useFilter || searchValue) {
|
||||||
generateLeafNodes({ id: 0 }, filter, searchValue)
|
// Flat filter mode
|
||||||
|
fetchData(null, filter, searchValue).then((data) => {
|
||||||
|
setTreeData(buildLeafNodes(data, 0))
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
handleTreeLoad(null)
|
handleTreeLoad(null)
|
||||||
}
|
}
|
||||||
@ -473,7 +327,8 @@ const ObjectSelect = ({
|
|||||||
useFilter,
|
useFilter,
|
||||||
filter,
|
filter,
|
||||||
searchValue,
|
searchValue,
|
||||||
generateLeafNodes,
|
buildLeafNodes,
|
||||||
|
fetchData,
|
||||||
handleTreeLoad,
|
handleTreeLoad,
|
||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
@ -481,10 +336,11 @@ const ObjectSelect = ({
|
|||||||
treeCheckable
|
treeCheckable
|
||||||
])
|
])
|
||||||
|
|
||||||
return error ? (
|
// --- Error UI ---
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
<Input value='Failed to load data.' status='error' disabled />
|
<Input value='Failed to load data.' status='error' disabled />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={<ReloadIcon />}
|
icon={<ReloadIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -494,7 +350,11 @@ const ObjectSelect = ({
|
|||||||
danger
|
danger
|
||||||
/>
|
/>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
) : (
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main TreeSelect UI ---
|
||||||
|
return (
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
treeDataSimpleMode
|
treeDataSimpleMode
|
||||||
treeDefaultExpandAll={true}
|
treeDefaultExpandAll={true}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import loglevel from 'loglevel'
|
|||||||
const logger = loglevel.getLogger('DasboardTable')
|
const logger = loglevel.getLogger('DasboardTable')
|
||||||
logger.setLevel(config.logLevel)
|
logger.setLevel(config.logLevel)
|
||||||
|
|
||||||
const DashboardTable = forwardRef(
|
const ObjectTable = forwardRef(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
columns,
|
columns,
|
||||||
@ -453,9 +453,9 @@ const DashboardTable = forwardRef(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
DashboardTable.displayName = 'DashboardTable'
|
ObjectTable.displayName = 'ObjectTable'
|
||||||
|
|
||||||
DashboardTable.propTypes = {
|
ObjectTable.propTypes = {
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
pageSize: PropTypes.number,
|
pageSize: PropTypes.number,
|
||||||
@ -467,4 +467,4 @@ DashboardTable.propTypes = {
|
|||||||
cardRenderer: PropTypes.func
|
cardRenderer: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DashboardTable
|
export default ObjectTable
|
||||||
@ -62,7 +62,7 @@ const PrinterJobsTree = ({
|
|||||||
<Space size={5}>
|
<Space size={5}>
|
||||||
<JobIcon />
|
<JobIcon />
|
||||||
{'Job'}
|
{'Job'}
|
||||||
<JobState job={job} />
|
<JobState state={job?.state} />
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
key: `job-${job._id}`,
|
key: `job-${job._id}`,
|
||||||
|
|||||||
@ -1,50 +1,18 @@
|
|||||||
// PrinterSelect.js
|
// PrinterSelect.js
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Progress, Flex, Space, Typography, Button, Tooltip } from 'antd'
|
import { Progress, Flex, Space } from 'antd'
|
||||||
import React, { useState, useContext, useEffect } from 'react'
|
import React from 'react'
|
||||||
import { PrintServerContext } from '../context/PrintServerContext'
|
|
||||||
import { CaretLeftOutlined } from '@ant-design/icons'
|
|
||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
|
||||||
import PauseIcon from '../../Icons/PauseIcon'
|
|
||||||
import StateTag from './StateTag'
|
import StateTag from './StateTag'
|
||||||
|
|
||||||
const PrinterState = ({
|
const PrinterState = ({ state, showProgress = true, showState = true }) => {
|
||||||
printer,
|
const currentState = state || {
|
||||||
showProgress = true,
|
|
||||||
showStatus = true,
|
|
||||||
showName = true,
|
|
||||||
showControls = true
|
|
||||||
}) => {
|
|
||||||
const { printServer } = useContext(PrintServerContext)
|
|
||||||
const [currentState, setCurrentState] = useState(
|
|
||||||
printer?.state || {
|
|
||||||
type: 'unknown',
|
type: 'unknown',
|
||||||
progress: 0
|
progress: 0
|
||||||
}
|
}
|
||||||
)
|
|
||||||
const [initialized, setInitialized] = useState(false)
|
|
||||||
const { Text } = Typography
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (printServer && !initialized && printer?.id) {
|
|
||||||
setInitialized(true)
|
|
||||||
printServer.on('notify_printer_update', (statusUpdate) => {
|
|
||||||
if (statusUpdate?._id === printer.id && statusUpdate?.state) {
|
|
||||||
setCurrentState(statusUpdate.state)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (printServer && initialized) {
|
|
||||||
printServer.off('notify_printer_update')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [printServer, initialized, printer?.id])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap='small' align={'center'}>
|
<Flex gap='small' align={'center'}>
|
||||||
{showName && <Text>{printer.name}</Text>}
|
{showState && (
|
||||||
{showStatus && (
|
|
||||||
<Space>
|
<Space>
|
||||||
<StateTag state={currentState.type} />
|
<StateTag state={currentState.type} />
|
||||||
</Space>
|
</Space>
|
||||||
@ -58,72 +26,14 @@ const PrinterState = ({
|
|||||||
style={{ width: '150px', marginBottom: '2px' }}
|
style={{ width: '150px', marginBottom: '2px' }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showControls && currentState.type === 'printing' ? (
|
|
||||||
<Space.Compact>
|
|
||||||
<Tooltip
|
|
||||||
title={currentState.type === 'printing' ? 'Pause' : 'Resume'}
|
|
||||||
arrow={false}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (currentState.type === 'printing') {
|
|
||||||
printServer.emit('printer.print.pause', {
|
|
||||||
printerId: printer.id
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
printServer.emit('printer.print.resume', {
|
|
||||||
printerId: printer.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{ height: '22px' }}
|
|
||||||
type='text'
|
|
||||||
icon={
|
|
||||||
currentState.type === 'printing' ? (
|
|
||||||
<PauseIcon
|
|
||||||
style={{ fontSize: '12px', marginBottom: '3px' }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<CaretLeftOutlined
|
|
||||||
style={{ fontSize: '10px', marginBottom: '3px' }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
></Button>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title='Cancel' arrow={false}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
printServer.emit('printer.print.cancel', {
|
|
||||||
printerId: printer.id
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
type='text'
|
|
||||||
style={{ height: '22px' }}
|
|
||||||
icon={
|
|
||||||
<XMarkIcon style={{ fontSize: '12px', marginBottom: '3px' }} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Space.Compact>
|
|
||||||
) : null}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PrinterState.propTypes = {
|
PrinterState.propTypes = {
|
||||||
printer: PropTypes.shape({
|
state: PropTypes.object,
|
||||||
id: PropTypes.string,
|
|
||||||
name: PropTypes.string,
|
|
||||||
state: PropTypes.shape({
|
|
||||||
type: PropTypes.string,
|
|
||||||
progress: PropTypes.number
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
showProgress: PropTypes.bool,
|
showProgress: PropTypes.bool,
|
||||||
showStatus: PropTypes.bool,
|
showState: PropTypes.bool
|
||||||
showName: PropTypes.bool,
|
|
||||||
showControls: PropTypes.bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PrinterState
|
export default PrinterState
|
||||||
|
|||||||
@ -7,24 +7,17 @@ import {
|
|||||||
Flex,
|
Flex,
|
||||||
Typography,
|
Typography,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Spin,
|
Spin
|
||||||
Badge
|
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import { LoadingOutlined, ExportOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import React, { useEffect, useState, useContext, useCallback } from 'react'
|
import React, { useEffect, useState, useContext, useCallback } from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
import IdDisplay from './IdDisplay'
|
import ObjectProperty from './ObjectProperty'
|
||||||
import TimeDisplay from './TimeDisplay'
|
|
||||||
import { Tag } from 'antd'
|
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
import PrinterState from './PrinterState'
|
|
||||||
import JobState from './JobState'
|
|
||||||
import FilamentStockState from './FilamentStockState'
|
|
||||||
import SubJobState from './SubJobState'
|
|
||||||
|
|
||||||
const { Text, Link } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const SpotlightTooltip = ({ query, type }) => {
|
const SpotlightTooltip = ({ query, type }) => {
|
||||||
const [spotlightData, setSpotlightData] = useState(null)
|
const [spotlightData, setSpotlightData] = useState(null)
|
||||||
@ -89,101 +82,18 @@ const SpotlightTooltip = ({ query, type }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to render value nicely
|
// Helper to determine property type based on key and value
|
||||||
const renderValue = (key, value) => {
|
const getPropertyType = (key, value) => {
|
||||||
if (key === '_id' || key === 'id') {
|
if (key === '_id') {
|
||||||
return (
|
return 'id'
|
||||||
<IdDisplay
|
|
||||||
id={value}
|
|
||||||
type={type}
|
|
||||||
showCopy={true}
|
|
||||||
longId={false}
|
|
||||||
showSpotlight={false}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (key === 'state') {
|
if (key === 'createdAt' || key === 'updatedAt') {
|
||||||
if (type === 'printer') {
|
return 'dateTime'
|
||||||
return (
|
|
||||||
<PrinterState
|
|
||||||
printer={spotlightData}
|
|
||||||
showControls={false}
|
|
||||||
showName={false}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (type === 'job') {
|
if (typeof value === 'boolean') {
|
||||||
return (
|
return 'bool'
|
||||||
<JobState job={spotlightData} showId={false} showQuantity={false} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (type === 'subjob') {
|
return key
|
||||||
return (
|
|
||||||
<SubJobState
|
|
||||||
subJob={spotlightData}
|
|
||||||
showId={false}
|
|
||||||
showQuantity={false}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (type === 'filamentstock') {
|
|
||||||
return (
|
|
||||||
<FilamentStockState
|
|
||||||
filamentStock={spotlightData}
|
|
||||||
showProgress={false}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (key === 'tags' && Array.isArray(value)) {
|
|
||||||
if (value.length == 0) {
|
|
||||||
return <Text>n/a</Text>
|
|
||||||
}
|
|
||||||
return value.map((tag) => (
|
|
||||||
<Tag key={tag} color='blue'>
|
|
||||||
{tag}
|
|
||||||
</Tag>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
if (key === 'email') {
|
|
||||||
return (
|
|
||||||
<Link href={`mailto:${value}`}>
|
|
||||||
{value + ' '}
|
|
||||||
<ExportOutlined />
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (key === 'color') {
|
|
||||||
return <Badge color={value} text={value} />
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof value === 'string' &&
|
|
||||||
value.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}/)
|
|
||||||
) {
|
|
||||||
// Format ISO date strings
|
|
||||||
return (
|
|
||||||
<TimeDisplay
|
|
||||||
dateTime={value}
|
|
||||||
showDate={true}
|
|
||||||
showTime={true}
|
|
||||||
showSince={true}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.join(', ')
|
|
||||||
}
|
|
||||||
if (typeof value === 'object' && value !== null) {
|
|
||||||
// For nested objects, show JSON string
|
|
||||||
return JSON.stringify(value)
|
|
||||||
}
|
|
||||||
if (value == '' || value.length == 0) {
|
|
||||||
return <Text>n/a</Text>
|
|
||||||
}
|
|
||||||
if (value != null) {
|
|
||||||
return <Text>{value.toString()}</Text>
|
|
||||||
}
|
|
||||||
return <Text>n/a</Text>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map of property names to user-friendly labels
|
// Map of property names to user-friendly labels
|
||||||
@ -234,10 +144,18 @@ const SpotlightTooltip = ({ query, type }) => {
|
|||||||
LABEL_MAP[key] || key.charAt(0).toUpperCase() + key.slice(1)
|
LABEL_MAP[key] || key.charAt(0).toUpperCase() + key.slice(1)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{renderValue(
|
<ObjectProperty
|
||||||
key,
|
type={getPropertyType(key)}
|
||||||
key === 'state' && value.type ? value.type : value
|
value={key == 'state' ? spotlightData : value}
|
||||||
)}
|
objectType={type}
|
||||||
|
isEditing={false}
|
||||||
|
longId={false}
|
||||||
|
showSpotlight={false}
|
||||||
|
showLabel={false}
|
||||||
|
showName={false}
|
||||||
|
showId={false}
|
||||||
|
showQuantity={false}
|
||||||
|
/>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
) : null
|
) : null
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Progress, Flex, Button, Space, Tooltip } from 'antd' // eslint-disable-line
|
import { Progress, Flex, Button, Space, Tooltip } from 'antd' // eslint-disable-line
|
||||||
import { CaretLeftOutlined } from '@ant-design/icons' // eslint-disable-line
|
import { CaretLeftOutlined } from '@ant-design/icons' // eslint-disable-line
|
||||||
import React, { useState, useContext, useEffect } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { PrintServerContext } from '../context/PrintServerContext'
|
import { PrintServerContext } from '../context/PrintServerContext'
|
||||||
import IdDisplay from './IdDisplay'
|
import IdDisplay from './IdDisplay'
|
||||||
import StateTag from './StateTag'
|
import StateTag from './StateTag'
|
||||||
@ -14,45 +14,22 @@ const logger = loglevel.getLogger('SubJobState')
|
|||||||
logger.setLevel(config.logLevel)
|
logger.setLevel(config.logLevel)
|
||||||
|
|
||||||
const SubJobState = ({
|
const SubJobState = ({
|
||||||
subJob,
|
state,
|
||||||
showStatus = true,
|
showStatus = true,
|
||||||
showId = true,
|
showId = true,
|
||||||
showProgress = true,
|
showProgress = true,
|
||||||
showControls = true //eslint-disable-line
|
showControls = true //eslint-disable-line
|
||||||
}) => {
|
}) => {
|
||||||
const { printServer } = useContext(PrintServerContext)
|
const { printServer } = useContext(PrintServerContext)
|
||||||
const [currentState, setCurrentState] = useState(
|
const currentState = state || {
|
||||||
subJob?.state || {
|
|
||||||
type: 'unknown',
|
type: 'unknown',
|
||||||
progress: 0
|
progress: 0
|
||||||
}
|
}
|
||||||
)
|
|
||||||
const [initialized, setInitialized] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (printServer && !initialized && subJob?._id) {
|
|
||||||
setInitialized(true)
|
|
||||||
logger.debug('on notify_subjob_update')
|
|
||||||
printServer.on('notify_subjob_update', (statusUpdate) => {
|
|
||||||
if (statusUpdate?._id === subJob._id && statusUpdate?.state) {
|
|
||||||
logger.debug('statusUpdate', statusUpdate)
|
|
||||||
setCurrentState(statusUpdate.state)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (printServer && initialized) {
|
|
||||||
logger.debug('off notify_subjob_update')
|
|
||||||
printServer.off('notify_subjob_update')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [printServer, initialized, subJob?._id])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap='small' align={'center'}>
|
<Flex gap='small' align={'center'}>
|
||||||
{showId && (
|
{showId && state?._id && (
|
||||||
<IdDisplay
|
<IdDisplay
|
||||||
id={subJob._id}
|
id={state._id}
|
||||||
showCopy={false}
|
showCopy={false}
|
||||||
type='subjob'
|
type='subjob'
|
||||||
longId={false}
|
longId={false}
|
||||||
@ -73,7 +50,8 @@ const SubJobState = ({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showControls &&
|
{showControls &&
|
||||||
(currentState.type === 'printing' || currentState.type === 'paused') ? (
|
(currentState.type === 'printing' || currentState.type === 'paused') &&
|
||||||
|
state?.printer ? (
|
||||||
<Space.Compact>
|
<Space.Compact>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={currentState.type === 'printing' ? 'Pause' : 'Resume'}
|
title={currentState.type === 'printing' ? 'Pause' : 'Resume'}
|
||||||
@ -83,11 +61,11 @@ const SubJobState = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (currentState.type === 'printing') {
|
if (currentState.type === 'printing') {
|
||||||
printServer.emit('printer.print.pause', {
|
printServer.emit('printer.print.pause', {
|
||||||
printerId: subJob.printer
|
printerId: state.printer
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
printServer.emit('printer.print.resume', {
|
printServer.emit('printer.print.resume', {
|
||||||
printerId: subJob.printer
|
printerId: state.printer
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -110,7 +88,7 @@ const SubJobState = ({
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
printServer.emit('printer.print.cancel', {
|
printServer.emit('printer.print.cancel', {
|
||||||
printerId: subJob.printer
|
printerId: state.printer
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
type='text'
|
type='text'
|
||||||
@ -122,12 +100,12 @@ const SubJobState = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
) : null}
|
) : null}
|
||||||
{showControls && currentState.type === 'queued' ? (
|
{showControls && currentState.type === 'queued' && state?._id ? (
|
||||||
<Tooltip title='Cancel' arrow={false}>
|
<Tooltip title='Cancel' arrow={false}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
printServer.emit('server.job_queue.cancel', {
|
printServer.emit('server.job_queue.cancel', {
|
||||||
subJobId: subJob._id
|
subJobId: state._id
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
style={{ height: '22px' }}
|
style={{ height: '22px' }}
|
||||||
@ -159,16 +137,7 @@ const SubJobState = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
SubJobState.propTypes = {
|
SubJobState.propTypes = {
|
||||||
subJob: PropTypes.shape({
|
state: PropTypes.object,
|
||||||
_id: PropTypes.string,
|
|
||||||
subJobId: PropTypes.string,
|
|
||||||
printer: PropTypes.string,
|
|
||||||
number: PropTypes.number,
|
|
||||||
state: PropTypes.shape({
|
|
||||||
type: PropTypes.string,
|
|
||||||
progress: PropTypes.number
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
showProgress: PropTypes.bool,
|
showProgress: PropTypes.bool,
|
||||||
showControls: PropTypes.bool,
|
showControls: PropTypes.bool,
|
||||||
showId: PropTypes.bool,
|
showId: PropTypes.bool,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// PrinterSelect.js
|
// PrinterSelect.js
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Tree, Card, Spin, Space, Button, message } from 'antd'
|
import { Tree, Card, Spin, Space, Button, message, Typography } from 'antd'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import React, { useState, useEffect, useContext, useCallback } from 'react'
|
import React, { useState, useEffect, useContext, useCallback } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
@ -11,6 +11,9 @@ import PrinterIcon from '../../Icons/PrinterIcon'
|
|||||||
import SubJobState from './SubJobState'
|
import SubJobState from './SubJobState'
|
||||||
import SubJobIcon from '../../Icons/SubJobIcon'
|
import SubJobIcon from '../../Icons/SubJobIcon'
|
||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
|
import IdDisplay from './IdDisplay'
|
||||||
|
|
||||||
|
const { Text } = Typography
|
||||||
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
|
|
||||||
@ -41,13 +44,11 @@ const SubJobsTree = ({ jobData, loading }) => {
|
|||||||
setExpandedKeys((prev) => [...prev, `printer-${printerData.id}`])
|
setExpandedKeys((prev) => [...prev, `printer-${printerData.id}`])
|
||||||
return {
|
return {
|
||||||
title: printerData.state ? (
|
title: printerData.state ? (
|
||||||
<Space size={5}>
|
<Space size={'small'}>
|
||||||
<PrinterIcon />
|
<PrinterIcon />
|
||||||
<PrinterState
|
<Text>{printerData.name}</Text>
|
||||||
printer={printerData}
|
<IdDisplay id={printerData._id} type='printer' longId={false} />
|
||||||
text={printerData.name}
|
<PrinterState state={printerData.state} showProgress={false} />
|
||||||
showProgress={false}
|
|
||||||
/>
|
|
||||||
</Space>
|
</Space>
|
||||||
) : (
|
) : (
|
||||||
<Spin indicator={<LoadingOutlined />} />
|
<Spin indicator={<LoadingOutlined />} />
|
||||||
@ -56,10 +57,12 @@ const SubJobsTree = ({ jobData, loading }) => {
|
|||||||
children: printerSubJobs.map((subJob) => {
|
children: printerSubJobs.map((subJob) => {
|
||||||
return {
|
return {
|
||||||
title: (
|
title: (
|
||||||
<Space>
|
<Space size={'small'}>
|
||||||
<SubJobIcon />
|
<SubJobIcon />
|
||||||
|
<Text>
|
||||||
{'Sub Job #' + subJob?.number.toString().padStart(2, '0')}
|
{'Sub Job #' + subJob?.number.toString().padStart(2, '0')}
|
||||||
<SubJobState subJob={subJob} showProgress={true} />
|
</Text>
|
||||||
|
<SubJobState state={subJob?.state} showProgress={true} />
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
key: `subjob-${subJob._id}`,
|
key: `subjob-${subJob._id}`,
|
||||||
@ -114,7 +117,6 @@ const SubJobsTree = ({ jobData, loading }) => {
|
|||||||
// Add printServer.io event listener for deployment updates
|
// Add printServer.io event listener for deployment updates
|
||||||
if (printServer) {
|
if (printServer) {
|
||||||
printServer.on('notify_deployment_update', (updateData) => {
|
printServer.on('notify_deployment_update', (updateData) => {
|
||||||
logger.debug('Received deployment update:', updateData)
|
|
||||||
setCurrentJobData((prevData) => {
|
setCurrentJobData((prevData) => {
|
||||||
if (!prevData) return prevData
|
if (!prevData) return prevData
|
||||||
|
|
||||||
@ -151,7 +153,6 @@ const SubJobsTree = ({ jobData, loading }) => {
|
|||||||
printServer.on('notify_subjob_update', (updateData) => {
|
printServer.on('notify_subjob_update', (updateData) => {
|
||||||
// Handle sub-job updates
|
// Handle sub-job updates
|
||||||
if (updateData.subJobId) {
|
if (updateData.subJobId) {
|
||||||
logger.debug('Received subjob update:', updateData)
|
|
||||||
setCurrentJobData((prevData) => {
|
setCurrentJobData((prevData) => {
|
||||||
if (!prevData) return prevData
|
if (!prevData) return prevData
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -19,7 +19,10 @@ import JobState from '../common/JobState'
|
|||||||
import IdDisplay from '../common/IdDisplay'
|
import IdDisplay from '../common/IdDisplay'
|
||||||
|
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
import { getTypeMeta, getPrefixMeta } from '../utils/Utils'
|
import {
|
||||||
|
getModelByName,
|
||||||
|
getModelByPrefix
|
||||||
|
} from '../../../database/ObjectModels'
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
import FilamentStockState from '../common/FilamentStockState'
|
import FilamentStockState from '../common/FilamentStockState'
|
||||||
import SubJobState from '../common/SubJobState'
|
import SubJobState from '../common/SubJobState'
|
||||||
@ -85,11 +88,11 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Check if it's a valid mode character
|
// Check if it's a valid mode character
|
||||||
if ([':', '?', '^'].includes(modeChar)) {
|
if ([':', '?', '^'].includes(modeChar)) {
|
||||||
const prefixMeta = getPrefixMeta(potentialPrefix)
|
const prefixModel = getModelByPrefix(potentialPrefix)
|
||||||
|
|
||||||
if (prefixMeta.prefix === potentialPrefix) {
|
if (prefixModel.prefix === potentialPrefix) {
|
||||||
return {
|
return {
|
||||||
...prefixMeta,
|
...prefixModel,
|
||||||
mode: modeChar
|
mode: modeChar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,7 +313,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Function to navigate to item URL
|
// Function to navigate to item URL
|
||||||
const navigateToItem = (item) => {
|
const navigateToItem = (item) => {
|
||||||
// Determine type for meta lookup
|
// Determine type for model lookup
|
||||||
let type = item.type || inputPrefix?.type
|
let type = item.type || inputPrefix?.type
|
||||||
// Fallback: try to infer type from known keys
|
// Fallback: try to infer type from known keys
|
||||||
if (!type) {
|
if (!type) {
|
||||||
@ -319,7 +322,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
// Add more inference as needed
|
// Add more inference as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = getTypeMeta(type)
|
const model = getModelByName(type)
|
||||||
|
|
||||||
// Get the appropriate ID for the item
|
// Get the appropriate ID for the item
|
||||||
let itemId = item._id || item.id
|
let itemId = item._id || item.id
|
||||||
@ -334,8 +337,8 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
itemId = item.job.id
|
itemId = item.job.id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemId && meta.url) {
|
if (itemId && model.url) {
|
||||||
const url = meta.url(itemId)
|
const url = model.url(itemId)
|
||||||
if (url && url !== '#') {
|
if (url && url !== '#') {
|
||||||
navigate(url)
|
navigate(url)
|
||||||
setShowModal(false)
|
setShowModal(false)
|
||||||
@ -451,7 +454,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
<List
|
<List
|
||||||
dataSource={listData}
|
dataSource={listData}
|
||||||
renderItem={(item, index) => {
|
renderItem={(item, index) => {
|
||||||
// Determine type for meta lookup
|
// Determine type for model lookup
|
||||||
let type = item.type || inputPrefix?.type
|
let type = item.type || inputPrefix?.type
|
||||||
// Fallback: try to infer type from known keys
|
// Fallback: try to infer type from known keys
|
||||||
if (!type) {
|
if (!type) {
|
||||||
@ -459,8 +462,8 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
else if (item.job) type = 'job'
|
else if (item.job) type = 'job'
|
||||||
// Add more inference as needed
|
// Add more inference as needed
|
||||||
}
|
}
|
||||||
const meta = getTypeMeta(type)
|
const model = getModelByName(type)
|
||||||
const Icon = meta.icon
|
const Icon = model.icon
|
||||||
|
|
||||||
// Determine shortcut text
|
// Determine shortcut text
|
||||||
let shortcutText = ''
|
let shortcutText = ''
|
||||||
@ -472,7 +475,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Item.Meta
|
<List.Item.Model
|
||||||
description={
|
description={
|
||||||
<Flex gap={'middle'} align='center'>
|
<Flex gap={'middle'} align='center'>
|
||||||
<Text>
|
<Text>
|
||||||
@ -483,7 +486,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
<Flex gap={'small'} style={{ marginBottom: '2px' }}>
|
<Flex gap={'small'} style={{ marginBottom: '2px' }}>
|
||||||
{item.name ? <Text>{item.name}</Text> : null}
|
{item.name ? <Text>{item.name}</Text> : null}
|
||||||
|
|
||||||
{meta.type == 'printer' ? (
|
{model.type == 'printer' ? (
|
||||||
<PrinterState
|
<PrinterState
|
||||||
printer={item}
|
printer={item}
|
||||||
showName={false}
|
showName={false}
|
||||||
@ -491,7 +494,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
showId={false}
|
showId={false}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{meta.type == 'job' ? (
|
{model.type == 'job' ? (
|
||||||
<JobState
|
<JobState
|
||||||
job={item}
|
job={item}
|
||||||
showQuantity={false}
|
showQuantity={false}
|
||||||
@ -500,7 +503,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{meta.type == 'subjob' ? (
|
{model.type == 'subjob' ? (
|
||||||
<SubJobState
|
<SubJobState
|
||||||
subJob={item}
|
subJob={item}
|
||||||
showProgress={false}
|
showProgress={false}
|
||||||
@ -508,7 +511,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{meta.type == 'filamentstock' ? (
|
{model.type == 'filamentstock' ? (
|
||||||
<Flex gap={'small'}>
|
<Flex gap={'small'}>
|
||||||
<FilamentStockState
|
<FilamentStockState
|
||||||
filamentStock={item}
|
filamentStock={item}
|
||||||
@ -519,7 +522,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
) : null}
|
) : null}
|
||||||
<IdDisplay
|
<IdDisplay
|
||||||
id={item._id}
|
id={item._id}
|
||||||
type={meta.type}
|
type={model.type}
|
||||||
longId={false}
|
longId={false}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -1,22 +1,3 @@
|
|||||||
import PrinterIcon from '../../Icons/PrinterIcon'
|
|
||||||
import FilamentIcon from '../../Icons/FilamentIcon'
|
|
||||||
import FilamentStockIcon from '../../Icons/FilamentStockIcon'
|
|
||||||
import GCodeFileIcon from '../../Icons/GCodeFileIcon'
|
|
||||||
import JobIcon from '../../Icons/JobIcon'
|
|
||||||
import PartIcon from '../../Icons/PartIcon'
|
|
||||||
import ProductIcon from '../../Icons/ProductIcon'
|
|
||||||
import VendorIcon from '../../Icons/VendorIcon'
|
|
||||||
import SubJobIcon from '../../Icons/SubJobIcon'
|
|
||||||
import StockEventIcon from '../../Icons/StockEventIcon'
|
|
||||||
import StockAuditIcon from '../../Icons/StockAuditIcon'
|
|
||||||
import PartStockIcon from '../../Icons/PartStockIcon'
|
|
||||||
import ProductStockIcon from '../../Icons/ProductStockIcon'
|
|
||||||
import AuditLogIcon from '../../Icons/AuditLogIcon'
|
|
||||||
import PersonIcon from '../../Icons/PersonIcon'
|
|
||||||
import NoteTypeIcon from '../../Icons/NoteTypeIcon'
|
|
||||||
import NoteIcon from '../../Icons/NoteIcon'
|
|
||||||
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
|
|
||||||
|
|
||||||
export function capitalizeFirstLetter(string) {
|
export function capitalizeFirstLetter(string) {
|
||||||
try {
|
try {
|
||||||
return string[0].toUpperCase() + string.slice(1)
|
return string[0].toUpperCase() + string.slice(1)
|
||||||
@ -49,190 +30,5 @@ export function timeStringToMinutes(timeString) {
|
|||||||
return Math.floor(totalMinutes)
|
return Math.floor(totalMinutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TYPE_META = [
|
// Re-export the functions for backward compatibility
|
||||||
{
|
export {}
|
||||||
type: 'printer',
|
|
||||||
title: 'Printer',
|
|
||||||
prefix: 'PRN',
|
|
||||||
icon: PrinterIcon,
|
|
||||||
url: (id) => `/dashboard/production/printers/info?printerId=${id}`,
|
|
||||||
properties: {
|
|
||||||
name: 'text'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'filament',
|
|
||||||
title: 'Filament',
|
|
||||||
prefix: 'FIL',
|
|
||||||
icon: FilamentIcon,
|
|
||||||
url: (id) => `/dashboard/management/filaments/info?filamentId=${id}`,
|
|
||||||
properties: {
|
|
||||||
id: 'id',
|
|
||||||
createdAt: 'dateTime',
|
|
||||||
name: 'text',
|
|
||||||
updatedAt: 'dateTime',
|
|
||||||
vendor: 'object', // objectType: vendor
|
|
||||||
vendorId: 'id', // objectType: vendor
|
|
||||||
type: 'material',
|
|
||||||
cost: 'currency',
|
|
||||||
color: 'color',
|
|
||||||
diameter: 'mm',
|
|
||||||
density: 'density',
|
|
||||||
url: 'text',
|
|
||||||
barcode: 'text'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'spool',
|
|
||||||
title: 'Spool',
|
|
||||||
prefix: 'SPL',
|
|
||||||
icon: FilamentIcon,
|
|
||||||
url: (id) => `/dashboard/inventory/spool/info?spoolId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'gcodefile',
|
|
||||||
title: 'GCode File',
|
|
||||||
prefix: 'GCF',
|
|
||||||
icon: GCodeFileIcon,
|
|
||||||
url: (id) => `/dashboard/production/gcodefiles/info?gcodeFileId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'job',
|
|
||||||
title: 'Job',
|
|
||||||
prefix: 'JOB',
|
|
||||||
icon: JobIcon,
|
|
||||||
url: (id) => `/dashboard/production/jobs/info?jobId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'part',
|
|
||||||
title: 'Part',
|
|
||||||
prefix: 'PRT',
|
|
||||||
icon: PartIcon,
|
|
||||||
url: (id) => `/dashboard/management/parts/info?partId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'product',
|
|
||||||
title: 'Product',
|
|
||||||
prefix: 'PRD',
|
|
||||||
icon: ProductIcon,
|
|
||||||
url: (id) => `/dashboard/management/products/info?productId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'vendor',
|
|
||||||
title: 'Vendor',
|
|
||||||
prefix: 'VEN',
|
|
||||||
icon: VendorIcon,
|
|
||||||
url: (id) => `/dashboard/management/vendors/info?vendorId=${id}`,
|
|
||||||
properties: {
|
|
||||||
id: 'id', // objectType: vendor
|
|
||||||
createdAt: 'dateTime',
|
|
||||||
name: 'text',
|
|
||||||
updatedAt: 'dateTime',
|
|
||||||
website: 'url',
|
|
||||||
country: 'country',
|
|
||||||
contact: 'text',
|
|
||||||
phone: 'text',
|
|
||||||
email: 'email'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'subjob',
|
|
||||||
title: 'Sub Job',
|
|
||||||
prefix: 'SJB',
|
|
||||||
icon: SubJobIcon,
|
|
||||||
url: () => `#`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'initial',
|
|
||||||
title: 'Initial',
|
|
||||||
prefix: 'INT',
|
|
||||||
icon: QuestionCircleIcon,
|
|
||||||
url: () => `#`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'filamentstock',
|
|
||||||
title: 'Filament Stock',
|
|
||||||
prefix: 'FLS',
|
|
||||||
icon: FilamentStockIcon,
|
|
||||||
url: (id) =>
|
|
||||||
`/dashboard/inventory/filamentstocks/info?filamentStockId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'stockevent',
|
|
||||||
title: 'Stock Event',
|
|
||||||
prefix: 'SEV',
|
|
||||||
icon: StockEventIcon,
|
|
||||||
url: () => `#`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'stockaudit',
|
|
||||||
title: 'Stock Audit',
|
|
||||||
prefix: 'SAU',
|
|
||||||
icon: StockAuditIcon,
|
|
||||||
url: (id) => `/dashboard/inventory/stockaudits/info?stockAuditId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'partstock',
|
|
||||||
title: 'Part Stock',
|
|
||||||
prefix: 'PTS',
|
|
||||||
icon: PartStockIcon,
|
|
||||||
url: (id) => `/dashboard/management/partstocks/info?partStockId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'productstock',
|
|
||||||
title: 'Product Stock',
|
|
||||||
prefix: 'PDS',
|
|
||||||
icon: ProductStockIcon,
|
|
||||||
url: (id) => `/dashboard/management/productstocks/info?productStockId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'auditlog',
|
|
||||||
title: 'Audit Log',
|
|
||||||
prefix: 'ADL',
|
|
||||||
icon: AuditLogIcon,
|
|
||||||
url: () => `#`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'user',
|
|
||||||
title: 'User',
|
|
||||||
prefix: 'USR',
|
|
||||||
icon: PersonIcon,
|
|
||||||
url: (id) => `/dashboard/management/users/info?userId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'notetype',
|
|
||||||
title: 'Note Type',
|
|
||||||
prefix: 'NTY',
|
|
||||||
icon: NoteTypeIcon,
|
|
||||||
url: (id) => `/dashboard/management/notetypes/info?noteTypeId=${id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'note',
|
|
||||||
title: 'Note',
|
|
||||||
prefix: 'NTE',
|
|
||||||
icon: NoteIcon,
|
|
||||||
url: () => `#`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export function getTypeMeta(type) {
|
|
||||||
return (
|
|
||||||
TYPE_META.find((meta) => meta.type === type) || {
|
|
||||||
type: 'unknown',
|
|
||||||
prefix: 'UNK',
|
|
||||||
icon: QuestionCircleIcon,
|
|
||||||
url: () => '#'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPrefixMeta(prefix) {
|
|
||||||
return (
|
|
||||||
TYPE_META.find((meta) => meta.prefix === prefix) || {
|
|
||||||
type: 'unknown',
|
|
||||||
prefix: 'UNK',
|
|
||||||
icon: QuestionCircleIcon,
|
|
||||||
url: () => '#'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const UrlDisplay = ({ url, showCopy = true, showLink = false }) => {
|
|||||||
rel='noopener noreferrer'
|
rel='noopener noreferrer'
|
||||||
style={{ marginRight: 8 }}
|
style={{ marginRight: 8 }}
|
||||||
>
|
>
|
||||||
{url}
|
<Text ellipsis>{url}</Text>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
131
src/database/ObjectModels.js
Normal file
131
src/database/ObjectModels.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { Printer } from './models/Printer.js'
|
||||||
|
import { Filament } from './models/Filament.js'
|
||||||
|
import { Spool } from './models/Spool'
|
||||||
|
import { GCodeFile } from './models/GCodeFile'
|
||||||
|
import { Job } from './models/Job'
|
||||||
|
import { Product } from './models/Product'
|
||||||
|
import { Vendor } from './models/Vendor'
|
||||||
|
import { SubJob } from './models/SubJob'
|
||||||
|
import { Initial } from './models/Initial'
|
||||||
|
import { FilamentStock } from './models/FilamentStock'
|
||||||
|
import { StockEvent } from './models/StockEvent'
|
||||||
|
import { StockAudit } from './models/StockAudit'
|
||||||
|
import { PartStock } from './models/PartStock'
|
||||||
|
import { ProductStock } from './models/ProductStock'
|
||||||
|
import { AuditLog } from './models/AuditLog'
|
||||||
|
import { User } from './models/User'
|
||||||
|
import { NoteType } from './models/NoteType'
|
||||||
|
import { Note } from './models/Note'
|
||||||
|
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
|
||||||
|
|
||||||
|
export const objectModels = [
|
||||||
|
Printer,
|
||||||
|
Filament,
|
||||||
|
Spool,
|
||||||
|
GCodeFile,
|
||||||
|
Job,
|
||||||
|
Product,
|
||||||
|
Vendor,
|
||||||
|
SubJob,
|
||||||
|
Initial,
|
||||||
|
FilamentStock,
|
||||||
|
StockEvent,
|
||||||
|
StockAudit,
|
||||||
|
PartStock,
|
||||||
|
ProductStock,
|
||||||
|
AuditLog,
|
||||||
|
User,
|
||||||
|
NoteType,
|
||||||
|
Note
|
||||||
|
]
|
||||||
|
|
||||||
|
// Re-export individual models for direct access
|
||||||
|
export {
|
||||||
|
Printer,
|
||||||
|
Filament,
|
||||||
|
Spool,
|
||||||
|
GCodeFile,
|
||||||
|
Job,
|
||||||
|
Product,
|
||||||
|
Vendor,
|
||||||
|
SubJob,
|
||||||
|
Initial,
|
||||||
|
FilamentStock,
|
||||||
|
StockEvent,
|
||||||
|
StockAudit,
|
||||||
|
PartStock,
|
||||||
|
ProductStock,
|
||||||
|
AuditLog,
|
||||||
|
User,
|
||||||
|
NoteType,
|
||||||
|
Note
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelByName(name) {
|
||||||
|
return (
|
||||||
|
objectModels.find((meta) => meta.name === name) || {
|
||||||
|
name: 'unknown',
|
||||||
|
label: 'Unknown',
|
||||||
|
prefix: 'UNK',
|
||||||
|
icon: QuestionCircleIcon,
|
||||||
|
url: () => '#',
|
||||||
|
properties: {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelProperties(name, propertyList) {
|
||||||
|
const model = getModelByName(name)
|
||||||
|
|
||||||
|
if (!model || !model.properties) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no propertyList is provided, return all properties
|
||||||
|
if (!propertyList || propertyList.length === 0) {
|
||||||
|
return model.properties
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a map of property names to properties for efficient lookup
|
||||||
|
const propertyMap = new Map(
|
||||||
|
model.properties.map((property) => [property.name, property])
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return properties in the same order as propertyList
|
||||||
|
return propertyList
|
||||||
|
.map((propertyName) => propertyMap.get(propertyName))
|
||||||
|
.filter((property) => property !== undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelByPrefix(prefix) {
|
||||||
|
return (
|
||||||
|
objectModels.find((meta) => meta.prefix === prefix) || {
|
||||||
|
name: 'unknown',
|
||||||
|
label: 'Unknown',
|
||||||
|
prefix: 'UNK',
|
||||||
|
icon: QuestionCircleIcon,
|
||||||
|
url: () => '#',
|
||||||
|
properties: {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function to get nested object values
|
||||||
|
export const getPropertyValue = (obj, path) => {
|
||||||
|
if (!obj || !path) return undefined
|
||||||
|
if (path.includes('.')) {
|
||||||
|
const propertyPath = path.split('.')
|
||||||
|
let currentValue = obj
|
||||||
|
for (const prop of propertyPath) {
|
||||||
|
if (currentValue && typeof currentValue === 'object') {
|
||||||
|
currentValue = currentValue[prop]
|
||||||
|
} else {
|
||||||
|
currentValue = undefined
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentValue
|
||||||
|
} else {
|
||||||
|
return obj[path]
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/database/models/AuditLog.js
Normal file
9
src/database/models/AuditLog.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import AuditLogIcon from '../../components/Icons/AuditLogIcon'
|
||||||
|
|
||||||
|
export const AuditLog = {
|
||||||
|
name: 'auditlog',
|
||||||
|
label: 'Audit Log',
|
||||||
|
prefix: 'ADL',
|
||||||
|
icon: AuditLogIcon,
|
||||||
|
url: () => `#`
|
||||||
|
}
|
||||||
100
src/database/models/Filament.js
Normal file
100
src/database/models/Filament.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import FilamentIcon from '../../components/Icons/FilamentIcon'
|
||||||
|
|
||||||
|
export const Filament = {
|
||||||
|
name: 'filament',
|
||||||
|
label: 'Filament',
|
||||||
|
prefix: 'FIL',
|
||||||
|
icon: FilamentIcon,
|
||||||
|
url: (id) => `/dashboard/management/filaments/info?filamentId=${id}`,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'filament',
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
|
||||||
|
required: true,
|
||||||
|
type: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vendor',
|
||||||
|
label: 'Vendor',
|
||||||
|
|
||||||
|
required: true,
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'vendor'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vendor._id',
|
||||||
|
label: 'Vendor ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'vendor',
|
||||||
|
showCopy: true,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
label: 'Material',
|
||||||
|
|
||||||
|
required: true,
|
||||||
|
type: 'material'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cost',
|
||||||
|
label: 'Cost',
|
||||||
|
|
||||||
|
required: true,
|
||||||
|
type: 'currency'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'color',
|
||||||
|
label: 'Color',
|
||||||
|
|
||||||
|
required: true,
|
||||||
|
type: 'color'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'diameter',
|
||||||
|
label: 'Diameter',
|
||||||
|
|
||||||
|
required: true,
|
||||||
|
type: 'mm'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'density',
|
||||||
|
label: 'Density',
|
||||||
|
required: true,
|
||||||
|
type: 'density'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'url',
|
||||||
|
label: 'Link',
|
||||||
|
type: 'url'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'barcode',
|
||||||
|
label: 'Barcode',
|
||||||
|
type: 'text'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
src/database/models/FilamentStock.js
Normal file
9
src/database/models/FilamentStock.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import FilamentStockIcon from '../../components/Icons/FilamentStockIcon'
|
||||||
|
|
||||||
|
export const FilamentStock = {
|
||||||
|
name: 'filamentstock',
|
||||||
|
label: 'Filament Stock',
|
||||||
|
prefix: 'FLS',
|
||||||
|
icon: FilamentStockIcon,
|
||||||
|
url: (id) => `/dashboard/inventory/filamentstocks/info?filamentStockId=${id}`
|
||||||
|
}
|
||||||
116
src/database/models/GCodeFile.js
Normal file
116
src/database/models/GCodeFile.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import GCodeFileIcon from '../../components/Icons/GCodeFileIcon'
|
||||||
|
|
||||||
|
export const GCodeFile = {
|
||||||
|
name: 'gcodeFile',
|
||||||
|
label: 'GCode File',
|
||||||
|
prefix: 'GCF',
|
||||||
|
icon: GCodeFileIcon,
|
||||||
|
url: (id) => `/dashboard/production/gcodefiles/info?gcodeFileId=${id}`,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'gcodefile',
|
||||||
|
value: null,
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
value: null,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
type: 'text',
|
||||||
|
value: null,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
value: null,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'filament',
|
||||||
|
label: 'Filament',
|
||||||
|
type: 'object',
|
||||||
|
value: null,
|
||||||
|
objectType: 'filament',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cost',
|
||||||
|
label: 'Cost',
|
||||||
|
type: 'currency',
|
||||||
|
value: null,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.estimatedPrintingTimeNormalMode',
|
||||||
|
label: 'Est Print Time',
|
||||||
|
value: null,
|
||||||
|
type: 'text',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.sparseInfillDensity',
|
||||||
|
label: 'Infill Density',
|
||||||
|
type: 'number',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.sparseInfillPattern',
|
||||||
|
label: 'Infill Pattern',
|
||||||
|
type: 'text',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.filamentUsedMm',
|
||||||
|
label: 'Filament Used (mm)',
|
||||||
|
value: null,
|
||||||
|
type: 'mm',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.filamentUsedG',
|
||||||
|
label: 'Filament Used (g)',
|
||||||
|
value: null,
|
||||||
|
type: 'weight',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.nozzleTemperature',
|
||||||
|
label: 'Hotend Temperature',
|
||||||
|
value: null,
|
||||||
|
type: 'number',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.hotPlateTemp',
|
||||||
|
label: 'Bed Temperature',
|
||||||
|
value: null,
|
||||||
|
type: 'number',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.filamentSettingsId',
|
||||||
|
label: 'Filament Profile',
|
||||||
|
value: null,
|
||||||
|
type: 'text',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFileInfo.printSettingsId',
|
||||||
|
label: 'Print Profile',
|
||||||
|
value: null,
|
||||||
|
type: 'text',
|
||||||
|
readOnly: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
src/database/models/Initial.js
Normal file
9
src/database/models/Initial.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import QuestionCircleIcon from '../../components/Icons/QuestionCircleIcon'
|
||||||
|
|
||||||
|
export const Initial = {
|
||||||
|
name: 'initial',
|
||||||
|
label: 'Initial',
|
||||||
|
prefix: 'INT',
|
||||||
|
icon: QuestionCircleIcon,
|
||||||
|
url: () => `#`
|
||||||
|
}
|
||||||
67
src/database/models/Job.js
Normal file
67
src/database/models/Job.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import JobIcon from '../../components/Icons/JobIcon'
|
||||||
|
|
||||||
|
export const Job = {
|
||||||
|
name: 'job',
|
||||||
|
label: 'Job',
|
||||||
|
prefix: 'JOB',
|
||||||
|
icon: JobIcon,
|
||||||
|
url: (id) => `/dashboard/production/jobs/info?jobId=${id}`,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'job',
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'state',
|
||||||
|
label: 'Status',
|
||||||
|
type: 'state',
|
||||||
|
objectType: 'job',
|
||||||
|
showStatus: true,
|
||||||
|
showProgress: true,
|
||||||
|
showId: false,
|
||||||
|
showQuantity: false,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFile',
|
||||||
|
label: 'GCode File',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'gcodeFile',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gcodeFile._id',
|
||||||
|
label: 'GCode File ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'gcodeFile',
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quantity',
|
||||||
|
label: 'Quantity',
|
||||||
|
type: 'number',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'startedAt',
|
||||||
|
label: 'Started At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'printers',
|
||||||
|
label: 'Assigned Printers',
|
||||||
|
type: 'number',
|
||||||
|
readOnly: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
src/database/models/Note.js
Normal file
9
src/database/models/Note.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import NoteIcon from '../../components/Icons/NoteIcon'
|
||||||
|
|
||||||
|
export const Note = {
|
||||||
|
name: 'note',
|
||||||
|
label: 'Note',
|
||||||
|
prefix: 'NTE',
|
||||||
|
icon: NoteIcon,
|
||||||
|
url: () => `#`
|
||||||
|
}
|
||||||
9
src/database/models/NoteType.js
Normal file
9
src/database/models/NoteType.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import NoteTypeIcon from '../../components/Icons/NoteTypeIcon'
|
||||||
|
|
||||||
|
export const NoteType = {
|
||||||
|
name: 'notetype',
|
||||||
|
label: 'Note Type',
|
||||||
|
prefix: 'NTY',
|
||||||
|
icon: NoteTypeIcon,
|
||||||
|
url: (id) => `/dashboard/management/notetypes/info?noteTypeId=${id}`
|
||||||
|
}
|
||||||
9
src/database/models/PartStock.js
Normal file
9
src/database/models/PartStock.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import PartStockIcon from '../../components/Icons/PartStockIcon'
|
||||||
|
|
||||||
|
export const PartStock = {
|
||||||
|
name: 'partstock',
|
||||||
|
label: 'Part Stock',
|
||||||
|
prefix: 'PTS',
|
||||||
|
icon: PartStockIcon,
|
||||||
|
url: (id) => `/dashboard/management/partstocks/info?partStockId=${id}`
|
||||||
|
}
|
||||||
91
src/database/models/Printer.js
Normal file
91
src/database/models/Printer.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import PrinterIcon from '../../components/Icons/PrinterIcon'
|
||||||
|
|
||||||
|
export const Printer = {
|
||||||
|
name: 'printer',
|
||||||
|
label: 'Printer',
|
||||||
|
prefix: 'PRN',
|
||||||
|
icon: PrinterIcon,
|
||||||
|
url: (id) => `/dashboard/production/printers/info?printerId=${id}`,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'printer',
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'connectedAt',
|
||||||
|
label: 'Connected At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
required: true,
|
||||||
|
type: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'state',
|
||||||
|
label: 'Status',
|
||||||
|
type: 'state',
|
||||||
|
objectType: 'printer',
|
||||||
|
showName: false,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vendor',
|
||||||
|
label: 'Vendor',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'vendor',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'moonraker.host',
|
||||||
|
label: 'Host',
|
||||||
|
type: 'text',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vendor._id',
|
||||||
|
label: 'Vendor ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'vendor',
|
||||||
|
showHyperlink: true,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'moonraker.port',
|
||||||
|
label: 'Port',
|
||||||
|
type: 'number',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'moonraker.apiKey',
|
||||||
|
label: 'API Key',
|
||||||
|
type: 'secret',
|
||||||
|
reveal: true,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'moonraker.protocol',
|
||||||
|
label: 'Protocol',
|
||||||
|
type: 'wsprotocol',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tags',
|
||||||
|
label: 'Tags',
|
||||||
|
type: 'tags',
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'firmware',
|
||||||
|
label: 'Firmware Version',
|
||||||
|
type: 'text',
|
||||||
|
required: false,
|
||||||
|
readOnly: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
src/database/models/Product.js
Normal file
9
src/database/models/Product.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import ProductIcon from '../../components/Icons/ProductIcon'
|
||||||
|
|
||||||
|
export const Product = {
|
||||||
|
name: 'product',
|
||||||
|
label: 'Product',
|
||||||
|
prefix: 'PRD',
|
||||||
|
icon: ProductIcon,
|
||||||
|
url: (id) => `/dashboard/management/products/info?productId=${id}`
|
||||||
|
}
|
||||||
9
src/database/models/ProductStock.js
Normal file
9
src/database/models/ProductStock.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import ProductStockIcon from '../../components/Icons/ProductStockIcon'
|
||||||
|
|
||||||
|
export const ProductStock = {
|
||||||
|
name: 'productstock',
|
||||||
|
label: 'Product Stock',
|
||||||
|
prefix: 'PDS',
|
||||||
|
icon: ProductStockIcon,
|
||||||
|
url: (id) => `/dashboard/management/productstocks/info?productStockId=${id}`
|
||||||
|
}
|
||||||
9
src/database/models/Spool.js
Normal file
9
src/database/models/Spool.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import FilamentIcon from '../../components/Icons/FilamentIcon'
|
||||||
|
|
||||||
|
export const Spool = {
|
||||||
|
name: 'spool',
|
||||||
|
label: 'Spool',
|
||||||
|
prefix: 'SPL',
|
||||||
|
icon: FilamentIcon,
|
||||||
|
url: (id) => `/dashboard/inventory/spool/info?spoolId=${id}`
|
||||||
|
}
|
||||||
9
src/database/models/StockAudit.js
Normal file
9
src/database/models/StockAudit.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import StockAuditIcon from '../../components/Icons/StockAuditIcon'
|
||||||
|
|
||||||
|
export const StockAudit = {
|
||||||
|
name: 'stockaudit',
|
||||||
|
label: 'Stock Audit',
|
||||||
|
prefix: 'SAU',
|
||||||
|
icon: StockAuditIcon,
|
||||||
|
url: (id) => `/dashboard/inventory/stockaudits/info?stockAuditId=${id}`
|
||||||
|
}
|
||||||
9
src/database/models/StockEvent.js
Normal file
9
src/database/models/StockEvent.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import StockEventIcon from '../../components/Icons/StockEventIcon'
|
||||||
|
|
||||||
|
export const StockEvent = {
|
||||||
|
name: 'stockevent',
|
||||||
|
label: 'Stock Event',
|
||||||
|
prefix: 'SEV',
|
||||||
|
icon: StockEventIcon,
|
||||||
|
url: () => `#`
|
||||||
|
}
|
||||||
9
src/database/models/SubJob.js
Normal file
9
src/database/models/SubJob.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import SubJobIcon from '../../components/Icons/SubJobIcon'
|
||||||
|
|
||||||
|
export const SubJob = {
|
||||||
|
name: 'subjob',
|
||||||
|
label: 'Sub Job',
|
||||||
|
prefix: 'SJB',
|
||||||
|
icon: SubJobIcon,
|
||||||
|
url: () => `#`
|
||||||
|
}
|
||||||
9
src/database/models/User.js
Normal file
9
src/database/models/User.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import PersonIcon from '../../components/Icons/PersonIcon'
|
||||||
|
|
||||||
|
export const User = {
|
||||||
|
name: 'user',
|
||||||
|
label: 'User',
|
||||||
|
prefix: 'USR',
|
||||||
|
icon: PersonIcon,
|
||||||
|
url: (id) => `/dashboard/management/users/info?userId=${id}`
|
||||||
|
}
|
||||||
73
src/database/models/Vendor.js
Normal file
73
src/database/models/Vendor.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import VendorIcon from '../../components/Icons/VendorIcon'
|
||||||
|
|
||||||
|
export const Vendor = {
|
||||||
|
name: 'vendor',
|
||||||
|
label: 'Vendor',
|
||||||
|
prefix: 'VEN',
|
||||||
|
icon: VendorIcon,
|
||||||
|
url: (id) => `/dashboard/management/vendors/info?vendorId=${id}`,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'vendor',
|
||||||
|
showCopy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
required: true,
|
||||||
|
type: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'contact',
|
||||||
|
label: 'Contact',
|
||||||
|
type: 'text',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'country',
|
||||||
|
label: 'Country',
|
||||||
|
type: 'country',
|
||||||
|
readOnly: false,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
label: 'Email',
|
||||||
|
type: 'email',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phone',
|
||||||
|
label: 'Phone',
|
||||||
|
type: 'phone',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'website',
|
||||||
|
label: 'Website',
|
||||||
|
type: 'url',
|
||||||
|
readOnly: false,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user