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 CheckIcon from '../../Icons/CheckIcon'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ListIcon from '../../Icons/ListIcon'
|
||||
import GridIcon from '../../Icons/GridIcon'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
@ -326,7 +326,7 @@ const FilamentStocks = () => {
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/filamentstocks`}
|
||||
|
||||
@ -27,7 +27,7 @@ import TimeDisplay from '../../common/TimeDisplay'
|
||||
import FilamentIcon from '../../../Icons/FilamentIcon'
|
||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
|
||||
import config from '../../../../config'
|
||||
import FilamentStockIcon from '../../../Icons/FilamentStockIcon'
|
||||
@ -424,7 +424,7 @@ const FilamentStockInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={filamentStockId} />
|
||||
<NotesPanel _id={filamentStockId} />
|
||||
</Card>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
|
||||
@ -14,7 +14,7 @@ import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import PartStockState from '../common/PartStockState'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
|
||||
import config from '../../../config'
|
||||
|
||||
@ -176,7 +176,7 @@ const PartStocks = () => {
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={columns}
|
||||
url={`${config.backendUrl}/partstocks`}
|
||||
|
||||
@ -11,7 +11,7 @@ import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
|
||||
import config from '../../../config'
|
||||
|
||||
@ -167,7 +167,7 @@ const StockAudits = () => {
|
||||
<Button>Actions</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={columns}
|
||||
url={`${config.backendUrl}/stockaudits`}
|
||||
|
||||
@ -19,13 +19,13 @@ import PlusMinusIcon from '../../Icons/PlusMinusIcon'
|
||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||
import CheckIcon from '../../Icons/CheckIcon'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import GridIcon from '../../Icons/GridIcon'
|
||||
import ListIcon from '../../Icons/ListIcon'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
|
||||
import config from '../../../config'
|
||||
import { getTypeMeta } from '../utils/Utils'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import StockEventIcon from '../../Icons/StockEventIcon'
|
||||
|
||||
const { Text } = Typography
|
||||
@ -53,7 +53,7 @@ const StockEvents = () => {
|
||||
fixed: 'left',
|
||||
sorter: true,
|
||||
render: (type) => {
|
||||
return <Text>{getTypeMeta(type?.toLowerCase()).title}</Text>
|
||||
return <Text>{getModelByName(type).title}</Text>
|
||||
},
|
||||
filterDropdown: ({
|
||||
setSelectedKeys,
|
||||
@ -128,7 +128,7 @@ const StockEvents = () => {
|
||||
) : null}
|
||||
{record.subJob?.number ? (
|
||||
<IdDisplay
|
||||
id={record.subJob.number.toString().padStart(6, '0')}
|
||||
id={record.subJob._id}
|
||||
longId={false}
|
||||
type={'subjob'}
|
||||
/>
|
||||
@ -310,7 +310,7 @@ const StockEvents = () => {
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/stockevents`}
|
||||
|
||||
@ -17,7 +17,7 @@ import IdDisplay from '../common/IdDisplay'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
|
||||
import config from '../../../config'
|
||||
import AuditLogIcon from '../../Icons/AuditLogIcon'
|
||||
@ -316,7 +316,7 @@ const AuditLogs = () => {
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/auditlogs`}
|
||||
|
||||
@ -7,17 +7,20 @@ import config from '../../../../config'
|
||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import EditObjectForm from '../../common/EditObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import LockIndicator from './LockIndicator'
|
||||
import {
|
||||
getModelProperties,
|
||||
getPropertyValue
|
||||
} from '../../../../database/ObjectModels'
|
||||
|
||||
const log = loglevel.getLogger('FilamentInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -115,101 +118,10 @@ const FilamentInfo = () => {
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
items={[
|
||||
{
|
||||
name: 'id',
|
||||
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'
|
||||
}
|
||||
]}
|
||||
items={getModelProperties('filament').map((prop) => ({
|
||||
...prop,
|
||||
value: getPropertyValue(objectData, prop.name)
|
||||
}))}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
|
||||
@ -221,7 +133,7 @@ const FilamentInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={filamentId} />
|
||||
<NotesPanel _id={filamentId} />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import { AuthContext } from '../context/AuthContext'
|
||||
import IdDisplay from '../common/IdDisplay'
|
||||
import NewNoteType from './NoteTypes/NewNoteType'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||
@ -304,7 +304,7 @@ const NoteTypes = () => {
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/notetypes`}
|
||||
|
||||
@ -18,7 +18,7 @@ import { DownloadOutlined } from '@ant-design/icons'
|
||||
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
import IdDisplay from '../common/IdDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import NewProduct from './Products/NewProduct'
|
||||
import PartIcon from '../../Icons/PartIcon'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
@ -310,7 +310,7 @@ const Parts = () => {
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/parts`}
|
||||
|
||||
@ -30,7 +30,7 @@ import CheckIcon from '../../../Icons/CheckIcon.jsx'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import TimeDisplay from '../../common/TimeDisplay.jsx'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
|
||||
import config from '../../../../config.js'
|
||||
import BoolDisplay from '../../common/BoolDisplay.jsx'
|
||||
@ -664,7 +664,7 @@ const PartInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={partId} />
|
||||
<NotesPanel _id={partId} />
|
||||
</Card>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
|
||||
@ -19,7 +19,7 @@ import { DownloadOutlined } from '@ant-design/icons'
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
import IdDisplay from '../common/IdDisplay'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import NewProduct from './Products/NewProduct'
|
||||
import ProductIcon from '../../Icons/ProductIcon'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
@ -353,7 +353,7 @@ const Products = () => {
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/products`}
|
||||
|
||||
@ -4,7 +4,7 @@ import { Space, Button, Flex, Dropdown, Card } from 'antd'
|
||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
@ -206,7 +206,7 @@ const ProductInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={productId} />
|
||||
<NotesPanel _id={productId} />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import { ExportOutlined } from '@ant-design/icons'
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
import IdDisplay from '../common/IdDisplay'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import PersonIcon from '../../Icons/PersonIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||
@ -367,7 +367,7 @@ const Users = () => {
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/users`}
|
||||
|
||||
@ -5,7 +5,7 @@ import { LoadingOutlined } from '@ant-design/icons'
|
||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
@ -178,7 +178,7 @@ const UserInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={userId} />
|
||||
<NotesPanel _id={userId} />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ import IdDisplay from '../common/IdDisplay'
|
||||
import NewVendor from './Vendors/NewVendor'
|
||||
import CountryDisplay from '../common/CountryDisplay'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import VendorIcon from '../../Icons/VendorIcon'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
@ -363,7 +363,7 @@ const Vendors = () => {
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/vendors`}
|
||||
|
||||
@ -7,17 +7,20 @@ import config from '../../../../config'
|
||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
|
||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import EditObjectForm from '../../common/EditObjectForm'
|
||||
import EditButtons from '../../common/EditButtons'
|
||||
import LockIndicator from '../Filaments/LockIndicator'
|
||||
import {
|
||||
getModelProperties,
|
||||
getPropertyValue
|
||||
} from '../../../../database/ObjectModels'
|
||||
|
||||
const log = loglevel.getLogger('VendorInfo')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -115,67 +118,10 @@ const VendorInfo = () => {
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
items={[
|
||||
{
|
||||
name: 'id',
|
||||
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'
|
||||
}
|
||||
]}
|
||||
items={getModelProperties('vendor').map((prop) => ({
|
||||
...prop,
|
||||
value: getPropertyValue(objectData, prop.name)
|
||||
}))}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
|
||||
@ -187,7 +133,7 @@ const VendorInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={vendorId} />
|
||||
<NotesPanel _id={vendorId} />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||
import CheckIcon from '../../Icons/CheckIcon'
|
||||
import TimeDisplay from '../common/TimeDisplay'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
import ListIcon from '../../Icons/ListIcon'
|
||||
import GridIcon from '../../Icons/GridIcon'
|
||||
import useViewMode from '../hooks/useViewMode'
|
||||
@ -381,7 +381,7 @@ const GCodeFiles = () => {
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/gcodefiles`}
|
||||
|
||||
@ -5,7 +5,7 @@ import { LoadingOutlined } from '@ant-design/icons'
|
||||
import ReloadIcon from '../../../Icons/ReloadIcon'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
@ -16,6 +16,10 @@ import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import GCodeFileIcon from '../../../Icons/GCodeFileIcon.jsx'
|
||||
import {
|
||||
getModelProperties,
|
||||
getPropertyValue
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
@ -117,120 +121,10 @@ const GCodeFileInfo = () => {
|
||||
loading={loading}
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
items={[
|
||||
{
|
||||
name: '_id',
|
||||
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
|
||||
}
|
||||
]}
|
||||
items={getModelProperties('gcodeFile').map((prop) => ({
|
||||
...prop,
|
||||
value: getPropertyValue(objectData, prop.name)
|
||||
}))}
|
||||
objectData={objectData}
|
||||
type='gcodefile'
|
||||
/>
|
||||
@ -266,7 +160,7 @@ const GCodeFileInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={gcodeFileId} />
|
||||
<NotesPanel _id={gcodeFileId} />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ import CheckCircleIcon from '../../Icons/CheckCircleIcon.jsx'
|
||||
import PauseCircleIcon from '../../Icons/PauseCircleIcon.jsx'
|
||||
import XMarkCircleIcon from '../../Icons/XMarkCircleIcon.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 GridIcon from '../../Icons/GridIcon.jsx'
|
||||
import useViewMode from '../hooks/useViewMode.js'
|
||||
@ -146,9 +146,10 @@ const Jobs = () => {
|
||||
{
|
||||
title: 'State',
|
||||
key: 'state',
|
||||
dataIndex: 'state',
|
||||
width: 240,
|
||||
render: (record) => {
|
||||
return <JobState job={record} showQuantity={false} showId={false} />
|
||||
render: (state) => {
|
||||
return <JobState state={state} showQuantity={false} showId={false} />
|
||||
},
|
||||
filterDropdown: ({
|
||||
setSelectedKeys,
|
||||
@ -393,7 +394,7 @@ const Jobs = () => {
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/jobs`}
|
||||
|
||||
@ -4,7 +4,7 @@ import { Space, Button, Flex, Dropdown, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
@ -17,6 +17,10 @@ import JobIcon from '../../../Icons/JobIcon'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon'
|
||||
import NoteIcon from '../../../Icons/NoteIcon'
|
||||
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
|
||||
import {
|
||||
getModelProperties,
|
||||
getPropertyValue
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
|
||||
const JobInfo = () => {
|
||||
const location = useLocation()
|
||||
@ -114,72 +118,10 @@ const JobInfo = () => {
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
type='job'
|
||||
items={[
|
||||
{
|
||||
name: '_id',
|
||||
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
|
||||
}
|
||||
]}
|
||||
items={getModelProperties('job').map((prop) => ({
|
||||
...prop,
|
||||
value: getPropertyValue(objectData, prop.name)
|
||||
}))}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
|
||||
@ -203,7 +145,7 @@ const JobInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={jobId} />
|
||||
<NotesPanel _id={jobId} />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ import PlusIcon from '../../Icons/PlusIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||
import CheckIcon from '../../Icons/CheckIcon'
|
||||
import DashboardTable from '../common/DashboardTable'
|
||||
import ObjectTable from '../common/ObjectTable'
|
||||
|
||||
import config from '../../../config'
|
||||
import GridIcon from '../../Icons/GridIcon'
|
||||
@ -83,15 +83,12 @@ const Printers = () => {
|
||||
},
|
||||
{
|
||||
title: 'State',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
width: 240,
|
||||
render: (record) => {
|
||||
render: (state) => {
|
||||
return (
|
||||
<PrinterState
|
||||
printer={record}
|
||||
showName={false}
|
||||
showControls={false}
|
||||
/>
|
||||
<PrinterState state={state} showName={false} showControls={false} />
|
||||
)
|
||||
}
|
||||
},
|
||||
@ -312,7 +309,7 @@ const Printers = () => {
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<DashboardTable
|
||||
<ObjectTable
|
||||
ref={tableRef}
|
||||
columns={visibleColumns}
|
||||
url={`${config.backendUrl}/printers`}
|
||||
|
||||
@ -4,7 +4,7 @@ import { Space, Button, Flex, Dropdown, Card } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import useCollapseState from '../../hooks/useCollapseState'
|
||||
import AuditLogTable from '../../common/AuditLogTable'
|
||||
import DashboardNotes from '../../common/DashboardNotes'
|
||||
import NotesPanel from '../../common/NotesPanel'
|
||||
import InfoCollapse from '../../common/InfoCollapse'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import ViewButton from '../../common/ViewButton'
|
||||
@ -16,6 +16,10 @@ import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
|
||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||
import {
|
||||
getModelProperties,
|
||||
getPropertyValue
|
||||
} from '../../../../database/ObjectModels.js'
|
||||
|
||||
const PrinterInfo = () => {
|
||||
const location = useLocation()
|
||||
@ -113,102 +117,10 @@ const PrinterInfo = () => {
|
||||
indicator={<LoadingOutlined />}
|
||||
isEditing={isEditing}
|
||||
type='printer'
|
||||
items={[
|
||||
{
|
||||
name: '_id',
|
||||
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
|
||||
}
|
||||
]}
|
||||
items={getModelProperties('printer').map((prop) => ({
|
||||
...prop,
|
||||
value: getPropertyValue(objectData, prop.name)
|
||||
}))}
|
||||
/>
|
||||
</InfoCollapse>
|
||||
|
||||
@ -233,7 +145,7 @@ const PrinterInfo = () => {
|
||||
key='notes'
|
||||
>
|
||||
<Card>
|
||||
<DashboardNotes _id={printerId} />
|
||||
<NotesPanel _id={printerId} />
|
||||
</Card>
|
||||
</InfoCollapse>
|
||||
|
||||
|
||||
@ -68,7 +68,10 @@ const formatValue = (value, propertyName) => {
|
||||
}
|
||||
|
||||
const AuditLogTable = forwardRef(
|
||||
({ items, loading, showTargetColumn, showOwnerColumn }, ref) => {
|
||||
(
|
||||
{ items, loading = false, showTargetColumn = true, showOwnerColumn = true },
|
||||
ref
|
||||
) => {
|
||||
const [sortedInfo, setSortedInfo] = useState({
|
||||
columnKey: 'createdAt',
|
||||
order: 'descend'
|
||||
@ -207,10 +210,4 @@ AuditLogTable.propTypes = {
|
||||
showOwnerColumn: PropTypes.bool
|
||||
}
|
||||
|
||||
AuditLogTable.defaultProps = {
|
||||
loading: false,
|
||||
showTargetColumn: true,
|
||||
showOwnerColumn: true
|
||||
}
|
||||
|
||||
export default AuditLogTable
|
||||
|
||||
@ -47,7 +47,7 @@ const ColorSelector = ({ value, onChange, disabled, required = false }) => {
|
||||
|
||||
ColorSelector.propTypes = {
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
required: PropTypes.bool
|
||||
}
|
||||
|
||||
@ -1,126 +1,26 @@
|
||||
import { TreeSelect } from 'antd'
|
||||
import React, { useEffect, useState, useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import axios from 'axios'
|
||||
import config from '../../../config'
|
||||
import ObjectSelect from './ObjectSelect'
|
||||
|
||||
import FilamentStockDisplay from './FilamentStockDisplay'
|
||||
|
||||
const FilamentStockSelect = ({ onChange, filter, useFilter, value }) => {
|
||||
const [filamentStocksTreeData, setFilamentStocksTreeData] = useState([])
|
||||
const [filamentStocksData, setFilamentStocksData] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [defaultValue, setDefaultValue] = useState(value)
|
||||
|
||||
const getFilamentStockTitle = (filamentStock) => {
|
||||
return (
|
||||
<FilamentStockDisplay filamentStock={filamentStock} showCopy={false} />
|
||||
)
|
||||
}
|
||||
|
||||
const fetchFilamentStocksData = async (property, filter) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await axios.get(`${config.backendUrl}/filamentstocks`, {
|
||||
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])
|
||||
|
||||
const FilamentStockSelect = ({
|
||||
onChange,
|
||||
filter = {},
|
||||
useFilter = false,
|
||||
value,
|
||||
disabled = false
|
||||
}) => {
|
||||
return (
|
||||
<TreeSelect
|
||||
treeDataSimpleMode
|
||||
value={defaultValue?._id}
|
||||
loadData={handleFilamentStocksTreeLoad}
|
||||
treeData={filamentStocksTreeData}
|
||||
onChange={handleOnChange}
|
||||
loading={loading}
|
||||
<ObjectSelect
|
||||
endpoint={`${config.backendUrl}/filamentstocks`}
|
||||
propertyOrder={['tags']}
|
||||
filter={filter}
|
||||
useFilter={useFilter}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
placeholder='Select a filament stock'
|
||||
type='filamentstock'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -129,12 +29,8 @@ FilamentStockSelect.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.object,
|
||||
filter: PropTypes.object,
|
||||
useFilter: PropTypes.bool
|
||||
}
|
||||
|
||||
FilamentStockSelect.defaultProps = {
|
||||
filter: {},
|
||||
useFilter: false
|
||||
useFilter: PropTypes.bool,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
export default FilamentStockSelect
|
||||
|
||||
@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom'
|
||||
import { useMediaQuery } from 'react-responsive'
|
||||
import CopyButton from './CopyButton'
|
||||
import SpotlightTooltip from './SpotlightTooltip'
|
||||
import { getTypeMeta } from '../utils/Utils'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
|
||||
const { Text, Link } = Typography
|
||||
|
||||
@ -21,10 +21,10 @@ const IdDisplay = ({
|
||||
const navigate = useNavigate()
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
|
||||
const meta = getTypeMeta(type)
|
||||
const prefix = meta.prefix
|
||||
const hyperlink = meta.url(id)
|
||||
const IconComponent = meta.icon
|
||||
const model = getModelByName(type)
|
||||
const prefix = model.prefix
|
||||
const hyperlink = model.url(id)
|
||||
const IconComponent = model.icon
|
||||
const icon = <IconComponent style={{ paddingTop: '4px' }} />
|
||||
|
||||
if (!id) {
|
||||
|
||||
@ -1,47 +1,22 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { Progress, Flex, Typography, Space } from 'antd'
|
||||
import React, { useState, useContext, useEffect } from 'react'
|
||||
import { PrintServerContext } from '../context/PrintServerContext'
|
||||
import IdDisplay from './IdDisplay'
|
||||
import { Progress, Flex, Space } from 'antd'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import StateTag from './StateTag'
|
||||
|
||||
const JobState = ({
|
||||
job,
|
||||
showProgress = true,
|
||||
showStatus = true,
|
||||
showId = true,
|
||||
showQuantity = true
|
||||
}) => {
|
||||
const { printServer } = useContext(PrintServerContext)
|
||||
const JobState = ({ state, showProgress = true, showState = true }) => {
|
||||
const [currentState, setCurrentState] = useState(
|
||||
job?.state || { type: 'unknown', progress: 0 }
|
||||
state || { type: 'unknown', progress: 0 }
|
||||
)
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
const { Text } = Typography
|
||||
|
||||
useEffect(() => {
|
||||
if (printServer && !initialized && job?._id) {
|
||||
setInitialized(true)
|
||||
printServer.on('notify_job_update', (statusUpdate) => {
|
||||
if (statusUpdate?._id === job._id && statusUpdate?.state) {
|
||||
setCurrentState(statusUpdate.state)
|
||||
}
|
||||
})
|
||||
if (state) {
|
||||
setCurrentState(state)
|
||||
}
|
||||
return () => {
|
||||
if (printServer && initialized) {
|
||||
printServer.off('notify_job_update')
|
||||
}
|
||||
}
|
||||
}, [printServer, initialized, job?._id])
|
||||
}, [state])
|
||||
|
||||
return (
|
||||
<Flex gap='small' align={'center'}>
|
||||
{showId && (
|
||||
<IdDisplay id={job._id} showCopy={false} type='job' longId={false} />
|
||||
)}
|
||||
{showQuantity && <Text>({job.quantity})</Text>}
|
||||
{showStatus && (
|
||||
{showState && (
|
||||
<Space>
|
||||
<StateTag state={currentState?.type} />
|
||||
</Space>
|
||||
@ -60,18 +35,9 @@ const JobState = ({
|
||||
}
|
||||
|
||||
JobState.propTypes = {
|
||||
job: PropTypes.shape({
|
||||
_id: PropTypes.string,
|
||||
quantity: PropTypes.number,
|
||||
state: PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
progress: PropTypes.number
|
||||
})
|
||||
}),
|
||||
state: PropTypes.object,
|
||||
showProgress: PropTypes.bool,
|
||||
showQuantity: PropTypes.bool,
|
||||
showId: PropTypes.bool,
|
||||
showStatus: PropTypes.bool
|
||||
showState: PropTypes.bool
|
||||
}
|
||||
|
||||
export default JobState
|
||||
|
||||
@ -259,7 +259,7 @@ NoteItem.propTypes = {
|
||||
onChildNoteAdded: PropTypes.func
|
||||
}
|
||||
|
||||
const DashboardNotes = ({ _id, onNewNote }) => {
|
||||
const NotesPanel = ({ _id, onNewNote }) => {
|
||||
const [newNoteOpen, setNewNoteOpen] = useState(false)
|
||||
const [showMarkdown, setShowMarkdown] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@ -686,9 +686,9 @@ const DashboardNotes = ({ _id, onNewNote }) => {
|
||||
)
|
||||
}
|
||||
|
||||
DashboardNotes.propTypes = {
|
||||
NotesPanel.propTypes = {
|
||||
_id: PropTypes.string.isRequired,
|
||||
onNewNote: PropTypes.func
|
||||
}
|
||||
|
||||
export default DashboardNotes
|
||||
export default NotesPanel
|
||||
@ -32,6 +32,7 @@ import ColorSelector from './ColorSelector'
|
||||
import SecretDisplay from './SecretDisplay'
|
||||
import EyeIcon from '../../Icons/EyeIcon'
|
||||
import EyeSlashIcon from '../../Icons/EyeSlashIcon'
|
||||
import FilamentStockState from './FilamentStockState'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
@ -57,8 +58,14 @@ const ObjectProperty = ({
|
||||
readOnly = false,
|
||||
...rest
|
||||
}) => {
|
||||
// Split the name by "." to handle nested object properties
|
||||
var formItemName = name
|
||||
|
||||
if (name?.includes('.')) {
|
||||
formItemName = name ? name.split('.') : undefined
|
||||
}
|
||||
|
||||
const renderProperty = () => {
|
||||
console.log('Rendering')
|
||||
if (!isEditing || readOnly) {
|
||||
switch (type) {
|
||||
case 'secret':
|
||||
@ -120,7 +127,11 @@ const ObjectProperty = ({
|
||||
}
|
||||
case 'number': {
|
||||
if (value != null) {
|
||||
return <Text>{value}</Text>
|
||||
if (Array.isArray(value)) {
|
||||
return <Text>{value.length}</Text>
|
||||
} else {
|
||||
return <Text>{value}</Text>
|
||||
}
|
||||
} else {
|
||||
return <Text type='secondary'>n/a</Text>
|
||||
}
|
||||
@ -151,17 +162,18 @@ const ObjectProperty = ({
|
||||
}
|
||||
}
|
||||
case 'state': {
|
||||
if (value && value?.state) {
|
||||
if (value && value?.type) {
|
||||
switch (objectType) {
|
||||
case 'printer':
|
||||
return <PrinterState printer={value} {...rest} />
|
||||
return <PrinterState state={value} {...rest} />
|
||||
case 'job':
|
||||
return <JobState job={value} {...rest} />
|
||||
case 'subjob':
|
||||
return <SubJobState subJob={value} {...rest} />
|
||||
|
||||
return <JobState state={value} {...rest} />
|
||||
case 'subJob':
|
||||
return <SubJobState state={value} {...rest} />
|
||||
case 'filamentStock':
|
||||
return <FilamentStockState state={value} {...rest} />
|
||||
default:
|
||||
return <Text type='secondary'>n/a</Text>
|
||||
return <Text type='secondary'>No Object Type Specified</Text>
|
||||
}
|
||||
} else {
|
||||
return <Text type='secondary'>n/a</Text>
|
||||
@ -251,7 +263,7 @@ const ObjectProperty = ({
|
||||
switch (type) {
|
||||
case 'secret':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<Input.Password
|
||||
placeholder={label}
|
||||
{...mergedFormItemProps}
|
||||
@ -263,7 +275,7 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'wsprotocol':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<Select
|
||||
defaultValue='ws'
|
||||
options={[
|
||||
@ -276,7 +288,7 @@ const ObjectProperty = ({
|
||||
case 'bool':
|
||||
return (
|
||||
<Form.Item
|
||||
name={name}
|
||||
name={formItemName}
|
||||
{...mergedFormItemProps}
|
||||
valuePropName='checked'
|
||||
>
|
||||
@ -286,7 +298,7 @@ const ObjectProperty = ({
|
||||
case 'dateTime':
|
||||
return (
|
||||
<Form.Item
|
||||
name={name}
|
||||
name={formItemName}
|
||||
{...mergedFormItemProps}
|
||||
getValueProps={(v) => ({ value: v ? dayjs(v) : null })}
|
||||
>
|
||||
@ -295,7 +307,7 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'currency':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<InputNumber
|
||||
prefix='£'
|
||||
suffix='/kg'
|
||||
@ -306,14 +318,14 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'country':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<CountrySelect />
|
||||
</Form.Item>
|
||||
)
|
||||
case 'color':
|
||||
return (
|
||||
<Form.Item
|
||||
name={name}
|
||||
name={formItemName}
|
||||
{...mergedFormItemProps}
|
||||
valuePropName='value'
|
||||
getValueFromEvent={(v) => v}
|
||||
@ -323,7 +335,7 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'weight':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<InputNumber
|
||||
suffix='g'
|
||||
style={{ width: '100%' }}
|
||||
@ -333,7 +345,7 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'number':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
placeholder={label}
|
||||
@ -343,13 +355,13 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'text':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<Input placeholder={label} {...mergedFormItemProps} />
|
||||
</Form.Item>
|
||||
)
|
||||
case 'material':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<Select options={MATERIAL_OPTIONS} placeholder={label} />
|
||||
</Form.Item>
|
||||
)
|
||||
@ -364,31 +376,31 @@ const ObjectProperty = ({
|
||||
switch (objectType) {
|
||||
case 'vendor':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<VendorSelect placeholder={label} />
|
||||
</Form.Item>
|
||||
)
|
||||
case 'printer':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<PrinterSelect placeholder={label} />
|
||||
</Form.Item>
|
||||
)
|
||||
case 'gcodefile':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<GCodeFileSelect placeholder={label} />
|
||||
</Form.Item>
|
||||
)
|
||||
case 'filament':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<FilamentSelect placeholder={label} />
|
||||
</Form.Item>
|
||||
)
|
||||
case 'part':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<PartSelect placeholder={label} />
|
||||
</Form.Item>
|
||||
)
|
||||
@ -398,7 +410,7 @@ const ObjectProperty = ({
|
||||
|
||||
case 'density':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<InputNumber
|
||||
suffix='g/cm³'
|
||||
style={{ width: '100%' }}
|
||||
@ -408,7 +420,7 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'mm':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<InputNumber
|
||||
suffix='mm'
|
||||
style={{ width: '100%' }}
|
||||
@ -418,13 +430,13 @@ const ObjectProperty = ({
|
||||
)
|
||||
case 'tags':
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<TagsInput />
|
||||
</Form.Item>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<Form.Item name={name} {...mergedFormItemProps}>
|
||||
<Form.Item name={formItemName} {...mergedFormItemProps}>
|
||||
<Input placeholder={label} {...mergedFormItemProps} />
|
||||
</Form.Item>
|
||||
)
|
||||
@ -437,18 +449,7 @@ const ObjectProperty = ({
|
||||
}
|
||||
|
||||
ObjectProperty.propTypes = {
|
||||
type: PropTypes.oneOf([
|
||||
'text',
|
||||
'number',
|
||||
'currency',
|
||||
'color',
|
||||
'weight',
|
||||
'vendor',
|
||||
'material',
|
||||
'id',
|
||||
'density',
|
||||
'mm'
|
||||
]),
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.any,
|
||||
isEditing: PropTypes.bool,
|
||||
formItemProps: PropTypes.object,
|
||||
@ -456,10 +457,8 @@ ObjectProperty.propTypes = {
|
||||
name: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
showLabel: PropTypes.bool,
|
||||
objectType: PropTypes.string.isRequired,
|
||||
objectType: PropTypes.string,
|
||||
readOnly: PropTypes.bool
|
||||
}
|
||||
|
||||
ObjectProperty.defaultProps = {}
|
||||
|
||||
export default ObjectProperty
|
||||
|
||||
@ -2,12 +2,41 @@ import React, { useEffect, useState, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { TreeSelect, Typography, Flex, Badge, Space, Button, Input } from 'antd'
|
||||
import axios from 'axios'
|
||||
import { getTypeMeta } from '../utils/Utils'
|
||||
import { getModelByName } from '../../../database/ObjectModels'
|
||||
import IdDisplay from './IdDisplay'
|
||||
import CountryDisplay from './CountryDisplay'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import ObjectProperty from './ObjectProperty'
|
||||
const { Text } = Typography
|
||||
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.
|
||||
*
|
||||
@ -35,29 +64,14 @@ const ObjectSelect = ({
|
||||
type = 'unknown',
|
||||
...rest
|
||||
}) => {
|
||||
// --- State ---
|
||||
const [treeData, setTreeData] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [defaultValue, setDefaultValue] = useState(treeCheckable ? [] : value)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
// Helper to get filter object for a node
|
||||
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
|
||||
// --- API: Fetch data for a property level or leaf ---
|
||||
const fetchData = useCallback(
|
||||
async (property, filter, search) => {
|
||||
setLoading(true)
|
||||
@ -74,14 +88,13 @@ const ObjectSelect = ({
|
||||
} catch (err) {
|
||||
setLoading(false)
|
||||
setError(true)
|
||||
// Optionally handle error
|
||||
return []
|
||||
}
|
||||
},
|
||||
[endpoint]
|
||||
)
|
||||
|
||||
// Fetch single object by ID
|
||||
// --- API: Fetch a single object by ID ---
|
||||
const fetchObjectById = useCallback(
|
||||
async (objectId) => {
|
||||
setLoading(true)
|
||||
@ -95,305 +108,134 @@ const ObjectSelect = ({
|
||||
} catch (err) {
|
||||
setLoading(false)
|
||||
setError(true)
|
||||
console.error('Failed to fetch object by ID:', err)
|
||||
return null
|
||||
}
|
||||
},
|
||||
[endpoint]
|
||||
)
|
||||
|
||||
// Helper to render the title for a node
|
||||
// --- Render node title ---
|
||||
const renderTitle = useCallback(
|
||||
(item, isLeaf) => {
|
||||
if (!isLeaf) {
|
||||
// For category nodes, check if it's a country property
|
||||
const currentProperty = propertyOrder[item.propertyId]
|
||||
if (currentProperty === 'country' && item.value) {
|
||||
return <CountryDisplay countryCode={item.value} />
|
||||
}
|
||||
// For other category nodes, just show the value
|
||||
return <Text>{item[propertyOrder[item.propertyId]] || item.value}</Text>
|
||||
}
|
||||
// For leaf nodes, show icon, name, and id
|
||||
const meta = getTypeMeta(type)
|
||||
const Icon = meta.icon
|
||||
return (
|
||||
<Flex gap={'small'} align='center' style={{ width: '100%' }}>
|
||||
{Icon && <Icon />}
|
||||
{item?.color && <Badge color={item.color}></Badge>}
|
||||
<Text ellipsis>{item.name || type.title}</Text>
|
||||
<IdDisplay id={item._id} longId={false} type={type} />
|
||||
</Flex>
|
||||
)
|
||||
},
|
||||
[propertyOrder, type]
|
||||
)
|
||||
|
||||
// Build tree path for a default object
|
||||
const buildTreePathForObject = useCallback(
|
||||
async (object) => {
|
||||
if (!object || !propertyOrder || propertyOrder.length === 0) return
|
||||
|
||||
const newNodes = []
|
||||
let currentPId = 0
|
||||
|
||||
// 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 {
|
||||
id: value,
|
||||
pId: currentPId,
|
||||
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 }
|
||||
(item) => {
|
||||
if (item.propertyType) {
|
||||
return (
|
||||
<ObjectProperty
|
||||
type={item.propertyType}
|
||||
value={item.value}
|
||||
objectType={type}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
filterObj = getFilter(node)
|
||||
propertyId = node.propertyId + 1
|
||||
const model = getModelByName(type)
|
||||
const Icon = model.icon
|
||||
return (
|
||||
<Flex gap={'small'} align='center' style={{ width: '100%' }}>
|
||||
{Icon && <Icon />}
|
||||
{item?.color && <Badge color={item.color}></Badge>}
|
||||
<Text ellipsis>{item.name || type.title}</Text>
|
||||
<IdDisplay id={item._id} longId={false} type={type} />
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
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)
|
||||
},
|
||||
[type]
|
||||
)
|
||||
|
||||
// --- Build tree nodes for a property level ---
|
||||
const buildCategoryNodes = useCallback(
|
||||
(data, propertyName, propertyId, parentId) => {
|
||||
return data.map((item) => {
|
||||
let resolved = resolvePropertyPath(item, propertyName)
|
||||
let value = resolved.value
|
||||
let propertyType = resolved.finalProperty
|
||||
return {
|
||||
id: value,
|
||||
pId: node.id,
|
||||
pId: parentId,
|
||||
value: value,
|
||||
key: value,
|
||||
propertyId: propertyId,
|
||||
title: title,
|
||||
title: renderTitle({ ...item, value, propertyType }),
|
||||
isLeaf: false,
|
||||
selectable: false,
|
||||
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(
|
||||
async (node) => {
|
||||
if (!propertyOrder.length) return
|
||||
if (node) {
|
||||
// Not at leaf level yet
|
||||
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 {
|
||||
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 {
|
||||
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) => {
|
||||
if (onChange) {
|
||||
if (treeCheckable) {
|
||||
// Handle multiple selections with checkboxes
|
||||
const selectedObjects = []
|
||||
// Multi-select
|
||||
let selectedObjects = []
|
||||
if (Array.isArray(val)) {
|
||||
val.forEach((selectedValue) => {
|
||||
selectedObjects = val.map((selectedValue) => {
|
||||
const node = treeData.find((n) => n.value === selectedValue)
|
||||
if (node) {
|
||||
selectedObjects.push(node.raw)
|
||||
} else {
|
||||
selectedObjects.push(selectedValue)
|
||||
}
|
||||
return node ? node.raw : selectedValue
|
||||
})
|
||||
}
|
||||
onChange(selectedObjects, selectedOptions)
|
||||
} else {
|
||||
// Handle single selection
|
||||
// Single select
|
||||
const node = treeData.find((n) => n.value === val)
|
||||
onChange(node ? node.raw : val, selectedOptions)
|
||||
}
|
||||
@ -401,21 +243,18 @@ const ObjectSelect = ({
|
||||
setDefaultValue(val)
|
||||
}
|
||||
|
||||
// Search handler
|
||||
// --- Search handler ---
|
||||
const handleSearch = (val) => {
|
||||
setSearchValue(val)
|
||||
setTreeData([])
|
||||
}
|
||||
|
||||
// Keep defaultValue in sync and handle object values
|
||||
// --- Sync defaultValue and load tree path for object values ---
|
||||
useEffect(() => {
|
||||
if (treeCheckable) {
|
||||
// Handle array of values for multi-select
|
||||
if (Array.isArray(value)) {
|
||||
const valueIds = value.map((v) => v._id || v.id || v)
|
||||
setDefaultValue(valueIds)
|
||||
|
||||
// Load tree paths for any objects that aren't already loaded
|
||||
value.forEach((item) => {
|
||||
if (item && typeof item === 'object' && item._id) {
|
||||
const existingNode = treeData.find(
|
||||
@ -424,7 +263,14 @@ const ObjectSelect = ({
|
||||
if (!existingNode) {
|
||||
fetchObjectById(item._id).then((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([])
|
||||
}
|
||||
} else {
|
||||
// Handle single value
|
||||
if (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)
|
||||
if (!existingNode) {
|
||||
fetchObjectById(value._id).then((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(() => {
|
||||
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) {
|
||||
return
|
||||
}
|
||||
|
||||
if (useFilter || searchValue) {
|
||||
generateLeafNodes({ id: 0 }, filter, searchValue)
|
||||
// Flat filter mode
|
||||
fetchData(null, filter, searchValue).then((data) => {
|
||||
setTreeData(buildLeafNodes(data, 0))
|
||||
})
|
||||
} else {
|
||||
handleTreeLoad(null)
|
||||
}
|
||||
@ -473,7 +327,8 @@ const ObjectSelect = ({
|
||||
useFilter,
|
||||
filter,
|
||||
searchValue,
|
||||
generateLeafNodes,
|
||||
buildLeafNodes,
|
||||
fetchData,
|
||||
handleTreeLoad,
|
||||
error,
|
||||
loading,
|
||||
@ -481,20 +336,25 @@ const ObjectSelect = ({
|
||||
treeCheckable
|
||||
])
|
||||
|
||||
return error ? (
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input value='Failed to load data.' status='error' disabled />
|
||||
// --- Error UI ---
|
||||
if (error) {
|
||||
return (
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input value='Failed to load data.' status='error' disabled />
|
||||
<Button
|
||||
icon={<ReloadIcon />}
|
||||
onClick={() => {
|
||||
setError(false)
|
||||
setTreeData([])
|
||||
}}
|
||||
danger
|
||||
/>
|
||||
</Space.Compact>
|
||||
)
|
||||
}
|
||||
|
||||
<Button
|
||||
icon={<ReloadIcon />}
|
||||
onClick={() => {
|
||||
setError(false)
|
||||
setTreeData([])
|
||||
}}
|
||||
danger
|
||||
/>
|
||||
</Space.Compact>
|
||||
) : (
|
||||
// --- Main TreeSelect UI ---
|
||||
return (
|
||||
<TreeSelect
|
||||
treeDataSimpleMode
|
||||
treeDefaultExpandAll={true}
|
||||
|
||||
@ -26,7 +26,7 @@ import loglevel from 'loglevel'
|
||||
const logger = loglevel.getLogger('DasboardTable')
|
||||
logger.setLevel(config.logLevel)
|
||||
|
||||
const DashboardTable = forwardRef(
|
||||
const ObjectTable = forwardRef(
|
||||
(
|
||||
{
|
||||
columns,
|
||||
@ -453,9 +453,9 @@ const DashboardTable = forwardRef(
|
||||
}
|
||||
)
|
||||
|
||||
DashboardTable.displayName = 'DashboardTable'
|
||||
ObjectTable.displayName = 'ObjectTable'
|
||||
|
||||
DashboardTable.propTypes = {
|
||||
ObjectTable.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
pageSize: PropTypes.number,
|
||||
@ -467,4 +467,4 @@ DashboardTable.propTypes = {
|
||||
cardRenderer: PropTypes.func
|
||||
}
|
||||
|
||||
export default DashboardTable
|
||||
export default ObjectTable
|
||||
@ -62,7 +62,7 @@ const PrinterJobsTree = ({
|
||||
<Space size={5}>
|
||||
<JobIcon />
|
||||
{'Job'}
|
||||
<JobState job={job} />
|
||||
<JobState state={job?.state} />
|
||||
</Space>
|
||||
),
|
||||
key: `job-${job._id}`,
|
||||
|
||||
@ -1,50 +1,18 @@
|
||||
// PrinterSelect.js
|
||||
import PropTypes from 'prop-types'
|
||||
import { Progress, Flex, Space, Typography, Button, Tooltip } from 'antd'
|
||||
import React, { useState, useContext, useEffect } from 'react'
|
||||
import { PrintServerContext } from '../context/PrintServerContext'
|
||||
import { CaretLeftOutlined } from '@ant-design/icons'
|
||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||
import PauseIcon from '../../Icons/PauseIcon'
|
||||
import { Progress, Flex, Space } from 'antd'
|
||||
import React from 'react'
|
||||
import StateTag from './StateTag'
|
||||
|
||||
const PrinterState = ({
|
||||
printer,
|
||||
showProgress = true,
|
||||
showStatus = true,
|
||||
showName = true,
|
||||
showControls = true
|
||||
}) => {
|
||||
const { printServer } = useContext(PrintServerContext)
|
||||
const [currentState, setCurrentState] = useState(
|
||||
printer?.state || {
|
||||
type: 'unknown',
|
||||
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])
|
||||
const PrinterState = ({ state, showProgress = true, showState = true }) => {
|
||||
const currentState = state || {
|
||||
type: 'unknown',
|
||||
progress: 0
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap='small' align={'center'}>
|
||||
{showName && <Text>{printer.name}</Text>}
|
||||
{showStatus && (
|
||||
{showState && (
|
||||
<Space>
|
||||
<StateTag state={currentState.type} />
|
||||
</Space>
|
||||
@ -58,72 +26,14 @@ const PrinterState = ({
|
||||
style={{ width: '150px', marginBottom: '2px' }}
|
||||
/>
|
||||
) : 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>
|
||||
)
|
||||
}
|
||||
|
||||
PrinterState.propTypes = {
|
||||
printer: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
state: PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
progress: PropTypes.number
|
||||
})
|
||||
}),
|
||||
state: PropTypes.object,
|
||||
showProgress: PropTypes.bool,
|
||||
showStatus: PropTypes.bool,
|
||||
showName: PropTypes.bool,
|
||||
showControls: PropTypes.bool
|
||||
showState: PropTypes.bool
|
||||
}
|
||||
|
||||
export default PrinterState
|
||||
|
||||
@ -7,24 +7,17 @@ import {
|
||||
Flex,
|
||||
Typography,
|
||||
Skeleton,
|
||||
Spin,
|
||||
Badge
|
||||
Spin
|
||||
} from 'antd'
|
||||
import { LoadingOutlined, ExportOutlined } from '@ant-design/icons'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import React, { useEffect, useState, useContext, useCallback } from 'react'
|
||||
import axios from 'axios'
|
||||
import { AuthContext } from '../context/AuthContext'
|
||||
import config from '../../../config'
|
||||
import IdDisplay from './IdDisplay'
|
||||
import TimeDisplay from './TimeDisplay'
|
||||
import { Tag } from 'antd'
|
||||
import ObjectProperty from './ObjectProperty'
|
||||
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 [spotlightData, setSpotlightData] = useState(null)
|
||||
@ -89,101 +82,18 @@ const SpotlightTooltip = ({ query, type }) => {
|
||||
)
|
||||
}
|
||||
|
||||
// Helper to render value nicely
|
||||
const renderValue = (key, value) => {
|
||||
if (key === '_id' || key === 'id') {
|
||||
return (
|
||||
<IdDisplay
|
||||
id={value}
|
||||
type={type}
|
||||
showCopy={true}
|
||||
longId={false}
|
||||
showSpotlight={false}
|
||||
/>
|
||||
)
|
||||
// Helper to determine property type based on key and value
|
||||
const getPropertyType = (key, value) => {
|
||||
if (key === '_id') {
|
||||
return 'id'
|
||||
}
|
||||
if (key === 'state') {
|
||||
if (type === 'printer') {
|
||||
return (
|
||||
<PrinterState
|
||||
printer={spotlightData}
|
||||
showControls={false}
|
||||
showName={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (type === 'job') {
|
||||
return (
|
||||
<JobState job={spotlightData} showId={false} showQuantity={false} />
|
||||
)
|
||||
}
|
||||
if (type === 'subjob') {
|
||||
return (
|
||||
<SubJobState
|
||||
subJob={spotlightData}
|
||||
showId={false}
|
||||
showQuantity={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (type === 'filamentstock') {
|
||||
return (
|
||||
<FilamentStockState
|
||||
filamentStock={spotlightData}
|
||||
showProgress={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (key === 'createdAt' || key === 'updatedAt') {
|
||||
return 'dateTime'
|
||||
}
|
||||
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 (typeof value === 'boolean') {
|
||||
return 'bool'
|
||||
}
|
||||
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>
|
||||
return key
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
>
|
||||
{renderValue(
|
||||
key,
|
||||
key === 'state' && value.type ? value.type : value
|
||||
)}
|
||||
<ObjectProperty
|
||||
type={getPropertyType(key)}
|
||||
value={key == 'state' ? spotlightData : value}
|
||||
objectType={type}
|
||||
isEditing={false}
|
||||
longId={false}
|
||||
showSpotlight={false}
|
||||
showLabel={false}
|
||||
showName={false}
|
||||
showId={false}
|
||||
showQuantity={false}
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
) : null
|
||||
)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { Progress, Flex, Button, Space, Tooltip } from 'antd' // 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 IdDisplay from './IdDisplay'
|
||||
import StateTag from './StateTag'
|
||||
@ -14,45 +14,22 @@ const logger = loglevel.getLogger('SubJobState')
|
||||
logger.setLevel(config.logLevel)
|
||||
|
||||
const SubJobState = ({
|
||||
subJob,
|
||||
state,
|
||||
showStatus = true,
|
||||
showId = true,
|
||||
showProgress = true,
|
||||
showControls = true //eslint-disable-line
|
||||
}) => {
|
||||
const { printServer } = useContext(PrintServerContext)
|
||||
const [currentState, setCurrentState] = useState(
|
||||
subJob?.state || {
|
||||
type: 'unknown',
|
||||
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])
|
||||
|
||||
const currentState = state || {
|
||||
type: 'unknown',
|
||||
progress: 0
|
||||
}
|
||||
return (
|
||||
<Flex gap='small' align={'center'}>
|
||||
{showId && (
|
||||
{showId && state?._id && (
|
||||
<IdDisplay
|
||||
id={subJob._id}
|
||||
id={state._id}
|
||||
showCopy={false}
|
||||
type='subjob'
|
||||
longId={false}
|
||||
@ -73,7 +50,8 @@ const SubJobState = ({
|
||||
/>
|
||||
) : null}
|
||||
{showControls &&
|
||||
(currentState.type === 'printing' || currentState.type === 'paused') ? (
|
||||
(currentState.type === 'printing' || currentState.type === 'paused') &&
|
||||
state?.printer ? (
|
||||
<Space.Compact>
|
||||
<Tooltip
|
||||
title={currentState.type === 'printing' ? 'Pause' : 'Resume'}
|
||||
@ -83,11 +61,11 @@ const SubJobState = ({
|
||||
onClick={() => {
|
||||
if (currentState.type === 'printing') {
|
||||
printServer.emit('printer.print.pause', {
|
||||
printerId: subJob.printer
|
||||
printerId: state.printer
|
||||
})
|
||||
} else {
|
||||
printServer.emit('printer.print.resume', {
|
||||
printerId: subJob.printer
|
||||
printerId: state.printer
|
||||
})
|
||||
}
|
||||
}}
|
||||
@ -110,7 +88,7 @@ const SubJobState = ({
|
||||
<Button
|
||||
onClick={() => {
|
||||
printServer.emit('printer.print.cancel', {
|
||||
printerId: subJob.printer
|
||||
printerId: state.printer
|
||||
})
|
||||
}}
|
||||
type='text'
|
||||
@ -122,12 +100,12 @@ const SubJobState = ({
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
) : null}
|
||||
{showControls && currentState.type === 'queued' ? (
|
||||
{showControls && currentState.type === 'queued' && state?._id ? (
|
||||
<Tooltip title='Cancel' arrow={false}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
printServer.emit('server.job_queue.cancel', {
|
||||
subJobId: subJob._id
|
||||
subJobId: state._id
|
||||
})
|
||||
}}
|
||||
style={{ height: '22px' }}
|
||||
@ -159,16 +137,7 @@ const SubJobState = ({
|
||||
}
|
||||
|
||||
SubJobState.propTypes = {
|
||||
subJob: PropTypes.shape({
|
||||
_id: PropTypes.string,
|
||||
subJobId: PropTypes.string,
|
||||
printer: PropTypes.string,
|
||||
number: PropTypes.number,
|
||||
state: PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
progress: PropTypes.number
|
||||
})
|
||||
}),
|
||||
state: PropTypes.object,
|
||||
showProgress: PropTypes.bool,
|
||||
showControls: PropTypes.bool,
|
||||
showId: PropTypes.bool,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// PrinterSelect.js
|
||||
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 React, { useState, useEffect, useContext, useCallback } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
@ -11,6 +11,9 @@ import PrinterIcon from '../../Icons/PrinterIcon'
|
||||
import SubJobState from './SubJobState'
|
||||
import SubJobIcon from '../../Icons/SubJobIcon'
|
||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||
import IdDisplay from './IdDisplay'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
import config from '../../../config'
|
||||
|
||||
@ -41,13 +44,11 @@ const SubJobsTree = ({ jobData, loading }) => {
|
||||
setExpandedKeys((prev) => [...prev, `printer-${printerData.id}`])
|
||||
return {
|
||||
title: printerData.state ? (
|
||||
<Space size={5}>
|
||||
<Space size={'small'}>
|
||||
<PrinterIcon />
|
||||
<PrinterState
|
||||
printer={printerData}
|
||||
text={printerData.name}
|
||||
showProgress={false}
|
||||
/>
|
||||
<Text>{printerData.name}</Text>
|
||||
<IdDisplay id={printerData._id} type='printer' longId={false} />
|
||||
<PrinterState state={printerData.state} showProgress={false} />
|
||||
</Space>
|
||||
) : (
|
||||
<Spin indicator={<LoadingOutlined />} />
|
||||
@ -56,10 +57,12 @@ const SubJobsTree = ({ jobData, loading }) => {
|
||||
children: printerSubJobs.map((subJob) => {
|
||||
return {
|
||||
title: (
|
||||
<Space>
|
||||
<Space size={'small'}>
|
||||
<SubJobIcon />
|
||||
{'Sub Job #' + subJob?.number.toString().padStart(2, '0')}
|
||||
<SubJobState subJob={subJob} showProgress={true} />
|
||||
<Text>
|
||||
{'Sub Job #' + subJob?.number.toString().padStart(2, '0')}
|
||||
</Text>
|
||||
<SubJobState state={subJob?.state} showProgress={true} />
|
||||
</Space>
|
||||
),
|
||||
key: `subjob-${subJob._id}`,
|
||||
@ -114,7 +117,6 @@ const SubJobsTree = ({ jobData, loading }) => {
|
||||
// Add printServer.io event listener for deployment updates
|
||||
if (printServer) {
|
||||
printServer.on('notify_deployment_update', (updateData) => {
|
||||
logger.debug('Received deployment update:', updateData)
|
||||
setCurrentJobData((prevData) => {
|
||||
if (!prevData) return prevData
|
||||
|
||||
@ -151,7 +153,6 @@ const SubJobsTree = ({ jobData, loading }) => {
|
||||
printServer.on('notify_subjob_update', (updateData) => {
|
||||
// Handle sub-job updates
|
||||
if (updateData.subJobId) {
|
||||
logger.debug('Received subjob update:', updateData)
|
||||
setCurrentJobData((prevData) => {
|
||||
if (!prevData) return prevData
|
||||
return {
|
||||
|
||||
@ -19,7 +19,10 @@ import JobState from '../common/JobState'
|
||||
import IdDisplay from '../common/IdDisplay'
|
||||
|
||||
import config from '../../../config'
|
||||
import { getTypeMeta, getPrefixMeta } from '../utils/Utils'
|
||||
import {
|
||||
getModelByName,
|
||||
getModelByPrefix
|
||||
} from '../../../database/ObjectModels'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||
import FilamentStockState from '../common/FilamentStockState'
|
||||
import SubJobState from '../common/SubJobState'
|
||||
@ -85,11 +88,11 @@ const SpotlightProvider = ({ children }) => {
|
||||
|
||||
// Check if it's a valid mode character
|
||||
if ([':', '?', '^'].includes(modeChar)) {
|
||||
const prefixMeta = getPrefixMeta(potentialPrefix)
|
||||
const prefixModel = getModelByPrefix(potentialPrefix)
|
||||
|
||||
if (prefixMeta.prefix === potentialPrefix) {
|
||||
if (prefixModel.prefix === potentialPrefix) {
|
||||
return {
|
||||
...prefixMeta,
|
||||
...prefixModel,
|
||||
mode: modeChar
|
||||
}
|
||||
}
|
||||
@ -310,7 +313,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
|
||||
// Function to navigate to item URL
|
||||
const navigateToItem = (item) => {
|
||||
// Determine type for meta lookup
|
||||
// Determine type for model lookup
|
||||
let type = item.type || inputPrefix?.type
|
||||
// Fallback: try to infer type from known keys
|
||||
if (!type) {
|
||||
@ -319,7 +322,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
// Add more inference as needed
|
||||
}
|
||||
|
||||
const meta = getTypeMeta(type)
|
||||
const model = getModelByName(type)
|
||||
|
||||
// Get the appropriate ID for the item
|
||||
let itemId = item._id || item.id
|
||||
@ -334,8 +337,8 @@ const SpotlightProvider = ({ children }) => {
|
||||
itemId = item.job.id
|
||||
}
|
||||
|
||||
if (itemId && meta.url) {
|
||||
const url = meta.url(itemId)
|
||||
if (itemId && model.url) {
|
||||
const url = model.url(itemId)
|
||||
if (url && url !== '#') {
|
||||
navigate(url)
|
||||
setShowModal(false)
|
||||
@ -451,7 +454,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
<List
|
||||
dataSource={listData}
|
||||
renderItem={(item, index) => {
|
||||
// Determine type for meta lookup
|
||||
// Determine type for model lookup
|
||||
let type = item.type || inputPrefix?.type
|
||||
// Fallback: try to infer type from known keys
|
||||
if (!type) {
|
||||
@ -459,8 +462,8 @@ const SpotlightProvider = ({ children }) => {
|
||||
else if (item.job) type = 'job'
|
||||
// Add more inference as needed
|
||||
}
|
||||
const meta = getTypeMeta(type)
|
||||
const Icon = meta.icon
|
||||
const model = getModelByName(type)
|
||||
const Icon = model.icon
|
||||
|
||||
// Determine shortcut text
|
||||
let shortcutText = ''
|
||||
@ -472,7 +475,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
|
||||
return (
|
||||
<List.Item>
|
||||
<List.Item.Meta
|
||||
<List.Item.Model
|
||||
description={
|
||||
<Flex gap={'middle'} align='center'>
|
||||
<Text>
|
||||
@ -483,7 +486,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
<Flex gap={'small'} style={{ marginBottom: '2px' }}>
|
||||
{item.name ? <Text>{item.name}</Text> : null}
|
||||
|
||||
{meta.type == 'printer' ? (
|
||||
{model.type == 'printer' ? (
|
||||
<PrinterState
|
||||
printer={item}
|
||||
showName={false}
|
||||
@ -491,7 +494,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
showId={false}
|
||||
/>
|
||||
) : null}
|
||||
{meta.type == 'job' ? (
|
||||
{model.type == 'job' ? (
|
||||
<JobState
|
||||
job={item}
|
||||
showQuantity={false}
|
||||
@ -500,7 +503,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{meta.type == 'subjob' ? (
|
||||
{model.type == 'subjob' ? (
|
||||
<SubJobState
|
||||
subJob={item}
|
||||
showProgress={false}
|
||||
@ -508,7 +511,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{meta.type == 'filamentstock' ? (
|
||||
{model.type == 'filamentstock' ? (
|
||||
<Flex gap={'small'}>
|
||||
<FilamentStockState
|
||||
filamentStock={item}
|
||||
@ -519,7 +522,7 @@ const SpotlightProvider = ({ children }) => {
|
||||
) : null}
|
||||
<IdDisplay
|
||||
id={item._id}
|
||||
type={meta.type}
|
||||
type={model.type}
|
||||
longId={false}
|
||||
/>
|
||||
</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) {
|
||||
try {
|
||||
return string[0].toUpperCase() + string.slice(1)
|
||||
@ -49,190 +30,5 @@ export function timeStringToMinutes(timeString) {
|
||||
return Math.floor(totalMinutes)
|
||||
}
|
||||
|
||||
export const TYPE_META = [
|
||||
{
|
||||
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: () => '#'
|
||||
}
|
||||
)
|
||||
}
|
||||
// Re-export the functions for backward compatibility
|
||||
export {}
|
||||
|
||||
@ -19,7 +19,7 @@ const UrlDisplay = ({ url, showCopy = true, showLink = false }) => {
|
||||
rel='noopener noreferrer'
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
{url}
|
||||
<Text ellipsis>{url}</Text>
|
||||
</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