Compare commits

...

18 Commits

Author SHA1 Message Date
e19200c059 Update DocumentTemplateInfo to set label width for improved layout consistency 2025-12-07 02:42:55 +00:00
8508442d2e Implement modal for adding new order items in PurchaseOrderInfo component
- Integrated NewOrderItem component within a modal to facilitate the addition of new order items.
- Managed modal visibility state with useState hook.
- Updated ObjectTable to display order items associated with the purchase order.
2025-12-07 02:42:50 +00:00
8945bb9a17 Update PartStockInfo to set label width for improved layout consistency 2025-12-07 02:42:44 +00:00
cd902d8f13 Add Order Items and Shipments to Inventory Sidebar and Update Purchase Order Info
- Enhanced InventorySidebar by adding new entries for Order Items and Shipments with corresponding icons and paths.
- Updated PurchaseOrderInfo to include a modal for adding new order items, integrating the NewOrderItem component and managing its visibility state.
- Adjusted label width in PartStockInfo and DocumentTemplateInfo for improved layout consistency.
2025-12-07 02:42:37 +00:00
aad5326c7e Update ObjectTypeSelect component to adjust label positioning for improved display
- Modified the label rendering in ObjectTypeSelect to include a style adjustment, positioning the ObjectTypeDisplay component slightly higher for better visual alignment.
2025-12-07 02:42:26 +00:00
f7532338b6 Refactor ObjectTable component to utilize refs for table filter and sorter management
- Replaced state management for tableFilter and tableSorter with refs for improved performance and reduced re-renders.
- Introduced tableData state to manage flattened data for display.
- Updated data fetching logic to reference current filter and sorter values from refs.
- Enhanced useEffect to flatten pages array for table display.
2025-12-07 02:42:18 +00:00
2879b8648d Enhance ObjectSelect component with value identity tracking and loading optimizations
- Introduced valueRef and prevValueIdentityRef to track changes in selected object values.
- Added getValueIdentity function to normalize object values for comparison.
- Updated treeSelectValue handling to prevent unnecessary fetches and improve performance.
- Enhanced useEffect dependencies for accurate value change detection and logging.
2025-12-07 02:42:10 +00:00
c10daf008e Enhance ObjectProperty component with readOnly handling and reference case support
- Added functionality to process readOnly as a function for dynamic object data handling.
- Updated rendering logic to include a new 'reference' case, allowing for the display of ID references with appropriate components.
- Improved ObjectDisplay rendering by passing showHyperlink prop for enhanced interactivity.
2025-12-07 02:42:02 +00:00
b955f42b88 Refactor ObjectForm component to improve computed value handling
- Introduced a working copy of currentData to facilitate sequential updates of computed values.
- Updated property processing logic to utilize workingData for accurate calculations.
- Removed unnecessary parameters in processProperty function for cleaner code.
- Enhanced dependency array in useEffect to include calculateComputedValues and model for better reactivity.
2025-12-07 02:41:55 +00:00
6da485bc8f Enhance IdDisplay component with reference and plainCode props
- Added reference prop to allow custom display of IDs.
- Introduced plainCode prop to toggle between code and text display styles.
- Updated rendering logic to accommodate new props for improved flexibility in ID presentation.
2025-12-07 02:41:45 +00:00
8b481a9617 Add routes for OrderItems and Shipments in InventoryRoutes
- Introduced new routes for OrderItems and OrderItemInfo components.
- Added routes for Shipments and ShipmentInfo components to enhance inventory management functionality.
2025-12-07 02:41:31 +00:00
f24bc27e2c Add OrderItem and Shipment models to database schema
- Introduced OrderItem and Shipment models with associated properties and actions.
- Updated ObjectModels to include new models for better integration.
- Refactored existing models to remove unnecessary ID references and improve hyperlink support for related objects.
- Enhanced various models by removing deprecated fields and ensuring consistency in object references.
2025-12-07 02:41:17 +00:00
2f323181f5 Refactor form components to accept defaultValues prop for enhanced flexibility
- Updated NewFilamentStock, NewPartStock, NewPurchaseOrder, NewStockAudit, NewCourierService, NewCourier, NewDocumentPrinter, NewDocumentSize, NewDocumentTemplate, NewPart, NewProduct, NewTaxRate, NewTaxRecord, NewVendor, NewGCodeFile, NewJob, and NewPrinter components to accept a defaultValues prop.
- Merged defaultValues with existing default values in each component to allow for more customizable form initialization.
2025-12-07 02:40:32 +00:00
dbb6be8654 Enhance App.css with additional styles for Ant Design components
- Added styles for .ant-card, .ant-progress, .ant-collapse, and .ant-radio-group.
- Updated .ant-select and related classes for improved layout and appearance.
- Introduced new styles for object display tags to enhance visual consistency.
2025-12-07 02:39:53 +00:00
3673a7a1ec Add Order Items and Shipments components with associated icons and forms
- Introduced OrderItems and Shipments components for managing order items and shipments.
- Added NewOrderItem and NewShipment forms for creating new entries.
- Implemented ObjectTable for displaying lists in different view modes.
- Created SVG icons for OrderItem and Shipment.
- Removed deprecated OrderItemsIcon component.
2025-12-07 02:39:45 +00:00
4995d37aa2 Removed console.log statements from various components to clean up the code and improve performance. 2025-12-07 02:38:39 +00:00
9f05bfd929 Enhanced ObjectDisplay component with spotlight data fetching and hyperlink support. Added props for showHyperlink and showSpotlight, and improved rendering logic for object names with optional popover tooltips. 2025-12-07 02:37:28 +00:00
863f0ad90c Added address to Vendor 2025-12-03 23:47:24 +00:00
70 changed files with 1992 additions and 749 deletions

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.607884,0,0,0.607884,2,6.470704)">
<path d="M48.669,64.047L37.295,64.047C35.825,69.222 31.078,73.031 25.453,73.031C19.815,73.031 15.034,69.222 13.552,64.047L10.547,64.047C3.781,64.047 0,60.266 0,53.516L0,11.688C0,4.938 3.781,1.141 10.547,1.141L57.859,1.141C64.609,1.141 68.391,4.938 68.391,11.688L68.391,18.422L76.766,18.422C80.406,18.422 82.859,19.344 85.031,21.781L96.078,34.266C98.109,36.547 98.703,38.266 98.703,41.906L98.703,47.796L90.672,43.432L90.672,42.578C90.672,41.062 90.219,39.797 89.312,38.766L79.844,28.141C78.703,26.859 77.484,26.453 75.859,26.453L68.391,26.453L68.391,41.102L60.359,45.47L60.359,12.328C60.359,10.141 59.281,9.141 57.172,9.141L11.203,9.141C9.078,9.141 8.031,10.141 8.031,12.328L8.031,52.859C8.031,55.063 9.078,56.031 11.203,56.031L13.984,56.031C15.827,51.524 20.28,48.344 25.453,48.344C30.614,48.344 35.037,51.524 36.866,56.031L48.805,56.031C48.713,56.664 48.669,57.335 48.669,58.048L48.669,64.047ZM71.404,39.488C71.395,39.38 71.391,39.269 71.391,39.156L71.391,29.766L75.031,29.766C76.172,29.766 76.938,30.188 77.719,31.062L86.219,40.641C86.421,40.864 86.586,41.062 86.706,41.277L84.098,39.859C80.017,37.638 75.532,37.513 71.404,39.488ZM25.453,66.422C28.609,66.422 31.156,63.828 31.156,60.656C31.156,57.5 28.609,54.906 25.453,54.906C22.281,54.906 19.688,57.5 19.688,60.656C19.688,63.828 22.281,66.422 25.453,66.422Z"/>
</g>
<path d="M36.262,57.455L47.835,63.705C48.595,64.12 49.484,64.12 50.258,63.705L61.815,57.455C63.32,56.645 64,55.748 64,53.768L64,41.758C64,40.158 63.393,39.124 61.975,38.357L51.928,32.897C50.069,31.885 48.016,31.885 46.157,32.897L36.117,38.357C34.693,39.124 34.085,40.157 34.085,41.757L34.085,53.767C34.085,55.747 34.772,56.644 36.262,57.455M38.295,54.7C37.585,54.313 37.325,53.89 37.325,53.2L37.325,43.393L47.35,48.882L47.35,59.694L38.295,54.7ZM59.79,54.7L50.728,59.694L50.728,48.882L60.76,43.393L60.76,53.201C60.76,53.889 60.5,54.313 59.79,54.701M49.043,45.941L39.177,40.559L42.447,38.751L52.341,44.125L49.043,45.941ZM55.798,42.274L45.918,36.893L47.632,35.939C48.572,35.429 49.498,35.408 50.452,35.939L58.915,40.559L55.798,42.274Z" style="fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -3,6 +3,7 @@
.ant-descriptions-item-content,
.ant-menu-title-content,
.ant-tag,
.ant-card,
.ant-checkbox-label,
.ant-btn,
.ant-breadcrumb-link,
@ -24,7 +25,11 @@
.ant-segmented-item-label,
.ant-badge-status-text,
.ant-tree-title,
.ant-select {
.ant-select,
.ant-progress,
.ant-collapse,
.ant-radio-group,
[class*=' ant-radio'] {
font-family: 'DM Sans';
}
@ -47,6 +52,15 @@
vertical-align: top !important;
}
.object-display-tag .ant-typography-ellipsis-single-line > code {
background-color: transparent !important;
border: 1px solid transparent !important;
max-width: calc(100%);
padding: 0;
margin: 0;
font-size: 95%;
}
.flag {
line-height: 0.7;
}
@ -346,12 +360,19 @@ body {
border-radius: 0px !important;
}
.ant-select-selection-item .ant-tag,
.ant-select-selection-item .object-display-tag,
.ant-select-tree-title .object-display-tag {
background: transparent !important;
padding: 0;
margin-right: 1px !important;
padding-top: 3px;
}
.ant-select-selection-item .object-display-tag {
top: 1.5px;
}
.ant-select-tree-title .object-display-tag {
top: 0.5px;
}
.ant-select-outlined.ant-select-multiple

View File

@ -3,12 +3,12 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewFilamentStock = ({ onOk, reset }) => {
const NewFilamentStock = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type={'filamentStock'}
reset={reset}
defaultValues={{ state: { type: 'unconsumed' } }}
defaultValues={{ state: { type: 'unconsumed' }, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
@ -64,7 +64,8 @@ const NewFilamentStock = ({ onOk, reset }) => {
NewFilamentStock.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewFilamentStock

View File

@ -7,6 +7,8 @@ import ProductStockIcon from '../../Icons/ProductStockIcon'
import StockEventIcon from '../../Icons/StockEventIcon'
import StockAuditIcon from '../../Icons/StockAuditIcon'
import PurchaseOrderIcon from '../../Icons/PurchaseOrderIcon'
import ShipmentIcon from '../../Icons/ShipmentIcon'
import OrderItemIcon from '../../Icons/OrderItemIcon'
const items = [
{
@ -41,6 +43,18 @@ const items = [
icon: <PurchaseOrderIcon />,
path: '/dashboard/inventory/purchaseorders'
},
{
key: 'orderitems',
label: 'Order Items',
icon: <OrderItemIcon />,
path: '/dashboard/inventory/orderitems'
},
{
key: 'shipments',
label: 'Shipments',
icon: <ShipmentIcon />,
path: '/dashboard/inventory/shipments'
},
{ type: 'divider' },
{
key: 'stockevents',
@ -63,7 +77,9 @@ const routeKeyMap = {
'/dashboard/inventory/productstocks': 'productstocks',
'/dashboard/inventory/stockevents': 'stockevents',
'/dashboard/inventory/stockaudits': 'stockaudits',
'/dashboard/inventory/purchaseorders': 'purchaseorders'
'/dashboard/inventory/purchaseorders': 'purchaseorders',
'/dashboard/inventory/orderitems': 'orderitems',
'/dashboard/inventory/shipments': 'shipments'
}
const InventorySidebar = (props) => {

View File

@ -0,0 +1,101 @@
import { useState, useRef } from 'react'
import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
import NewOrderItem from './OrderItems/NewOrderItem'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
const OrderItems = () => {
const [messageApi, contextHolder] = message.useMessage()
const [newOrderItemOpen, setNewOrderItemOpen] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('orderItems')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('orderItems')
const actionItems = {
items: [
{
label: 'New Order Item',
key: 'newOrderItem',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newOrderItem') {
setNewOrderItemOpen(true)
}
}
}
return (
<>
<Flex vertical={'true'} gap='large'>
{contextHolder}
<Flex justify={'space-between'}>
<Space size='small'>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='orderItem'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
</Space>
<Space>
<Button
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
onClick={() =>
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='orderItem'
cards={viewMode === 'cards'}
/>
</Flex>
<Modal
open={newOrderItemOpen}
styles={{ content: { paddingBottom: '24px' } }}
footer={null}
width={800}
onCancel={() => {
setNewOrderItemOpen(false)
}}
destroyOnHidden={true}
>
<NewOrderItem
onOk={() => {
setNewOrderItemOpen(false)
messageApi.success('New order item created successfully.')
tableRef.current?.reload()
}}
reset={newOrderItemOpen}
/>
</Modal>
</>
)
}
export default OrderItems

View File

@ -0,0 +1,99 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewOrderItem = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type={'orderItem'}
reset={reset}
defaultValues={{ syncAmount: null, quantity: 1, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='orderItem'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{
syncAmount: false,
itemAmount: false,
totalAmount: false,
totalAmountWithTax: false,
quantity: false
}}
/>
)
},
{
title: 'Pricing',
key: 'pricing',
content: (
<ObjectInfo
type='orderItem'
column={1}
bordered={false}
isEditing={true}
objectData={objectData}
visibleProperties={{
syncAmount: true,
itemAmount: true,
quantity: true,
taxRate: true,
totalAmountWithTax: true,
totalAmount: true
}}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='orderItem'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Order Item'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>
)
}
NewOrderItem.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewOrderItem

View File

@ -0,0 +1,202 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.js'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('OrderItemInfo')
log.setLevel(config.logLevel)
const OrderItemInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const orderItemId = new URLSearchParams(location.search).get('orderItemId')
const [collapseState, updateCollapseState] = useCollapseState(
'OrderItemInfo',
{
info: true,
notes: true,
auditLogs: true
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef?.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
}
}
return (
<>
<Flex
gap='large'
vertical='true'
style={{
maxHeight: '100%',
minHeight: 0
}}
>
<Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='orderItem'
id={orderItemId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Order Item Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={orderItemId}
type='orderItem'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap={'large'}>
<InfoCollapse
title='Order Item Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='orderItem'
objectData={objectData}
labelWidth='200px'
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={orderItemId} type='orderItem' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': orderItemId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default OrderItemInfo

View File

@ -3,12 +3,12 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewPartStock = ({ onOk, reset }) => {
const NewPartStock = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type={'partStock'}
reset={reset}
defaultValues={{ state: { type: 'new' } }}
defaultValues={{ state: { type: 'new' }, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
@ -64,7 +64,8 @@ const NewPartStock = ({ onOk, reset }) => {
NewPartStock.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewPartStock

View File

@ -157,6 +157,7 @@ const PartStockInfo = () => {
isEditing={isEditing}
type='partStock'
objectData={objectData}
labelWidth='175px'
visibleProperties={{
content: false,
testObject: false

View File

@ -5,12 +5,12 @@ import WizardView from '../../common/WizardView'
import { getModelProperty } from '../../../../database/ObjectModels.js'
import ObjectProperty from '../../common/ObjectProperty.jsx'
const NewPurchaseOrder = ({ onOk, reset }) => {
const NewPurchaseOrder = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type={'purchaseOrder'}
reset={reset}
defaultValues={{ state: { type: 'new' } }}
defaultValues={{ state: { type: 'new' }, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
@ -83,7 +83,8 @@ const NewPurchaseOrder = ({ onOk, reset }) => {
NewPurchaseOrder.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewPurchaseOrder

View File

@ -1,6 +1,6 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import { Space, Flex, Card, Modal } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
@ -21,9 +21,8 @@ import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelProperty } from '../../../../database/ObjectModels.js'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import OrderItemsIcon from '../../../Icons/OrderItemsIcon.jsx'
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
import NewOrderItem from '../OrderItems/NewOrderItem.jsx'
const log = loglevel.getLogger('PurchaseOrderInfo')
log.setLevel(config.logLevel)
@ -32,6 +31,7 @@ const PurchaseOrderInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const [newOrderItemOpen, setNewOrderItemOpen] = useState(false)
const purchaseOrderId = new URLSearchParams(location.search).get(
'purchaseOrderId'
)
@ -73,6 +73,10 @@ const PurchaseOrderInfo = () => {
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
},
newOrderItem: () => {
setNewOrderItemOpen(true)
return true
}
}
@ -180,11 +184,10 @@ const PurchaseOrderInfo = () => {
}
collapseKey='info'
>
<ObjectProperty
{...getModelProperty('purchaseOrder', 'items')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
<ObjectTable
type='orderItem'
masterFilter={{ 'order._id': purchaseOrderId }}
visibleColumns={{ order: false }}
/>
</InfoCollapse>
</Flex>
@ -224,6 +227,27 @@ const PurchaseOrderInfo = () => {
</Flex>
</ScrollBox>
</Flex>
<Modal
open={newOrderItemOpen}
onCancel={() => {
setNewOrderItemOpen(false)
}}
width={800}
footer={null}
destroyOnHidden={true}
>
<NewOrderItem
onOk={() => {
setNewOrderItemOpen(false)
}}
reset={newOrderItemOpen}
defaultValues={{
order: { _id: purchaseOrderId },
orderType: 'purchaseOrder',
syncAmount: 'itemCost'
}}
/>
</Modal>
</>
)
}

View File

@ -0,0 +1,101 @@
import { useState, useRef } from 'react'
import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
import NewShipment from './Shipments/NewShipment'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
const Shipments = () => {
const [messageApi, contextHolder] = message.useMessage()
const [newShipmentOpen, setNewShipmentOpen] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('shipments')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('shipments')
const actionItems = {
items: [
{
label: 'New Shipment',
key: 'newShipment',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newShipment') {
setNewShipmentOpen(true)
}
}
}
return (
<>
<Flex vertical={'true'} gap='large'>
{contextHolder}
<Flex justify={'space-between'}>
<Space size='small'>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='shipment'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
</Space>
<Space>
<Button
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
onClick={() =>
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='shipment'
cards={viewMode === 'cards'}
/>
</Flex>
<Modal
open={newShipmentOpen}
styles={{ content: { paddingBottom: '24px' } }}
footer={null}
width={800}
onCancel={() => {
setNewShipmentOpen(false)
}}
destroyOnHidden={true}
>
<NewShipment
onOk={() => {
setNewShipmentOpen(false)
messageApi.success('New shipment created successfully.')
tableRef.current?.reload()
}}
reset={newShipmentOpen}
/>
</Modal>
</>
)
}
export default Shipments

View File

@ -0,0 +1,94 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
import { getModelProperty } from '../../../../database/ObjectModels.js'
import ObjectProperty from '../../common/ObjectProperty.jsx'
const NewShipment = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type={'shipment'}
reset={reset}
defaultValues={{ state: { type: 'pending' }, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='shipment'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{
items: false,
cost: false,
shippedDate: false,
expectedDeliveryDate: false,
actualDeliveryDate: false,
notes: false
}}
/>
)
},
{
title: 'Items',
key: 'items',
content: (
<ObjectProperty
{...getModelProperty('shipment', 'items')}
isEditing={true}
objectData={objectData}
loading={submitLoading}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='shipment'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false,
items: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Shipment'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>
)
}
NewShipment.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewShipment

View File

@ -0,0 +1,229 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.js'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelProperty } from '../../../../database/ObjectModels.js'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
const log = loglevel.getLogger('ShipmentInfo')
log.setLevel(config.logLevel)
const ShipmentInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const shipmentId = new URLSearchParams(location.search).get('shipmentId')
const [collapseState, updateCollapseState] = useCollapseState(
'ShipmentInfo',
{
info: true,
notes: true,
auditLogs: true
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef?.current?.handleUpdate?.()
return true
},
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
}
}
return (
<>
<Flex
gap='large'
vertical='true'
style={{
maxHeight: '100%',
minHeight: 0
}}
>
<Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='shipment'
id={shipmentId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Shipment Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<DocumentPrintButton
type='shipment'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={shipmentId}
type='shipment'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap={'large'}>
<InfoCollapse
title='Shipment Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='shipment'
objectData={objectData}
visibleProperties={{
items: false
}}
/>
</InfoCollapse>
<InfoCollapse
title='Shipment Items'
icon={<OrderItemsIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectProperty
{...getModelProperty('shipment', 'items')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={shipmentId} type='shipment' />
</Card>
</InfoCollapse>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{ 'parent._id': shipmentId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default ShipmentInfo

View File

@ -3,12 +3,12 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewStockAudit = ({ onOk, reset }) => {
const NewStockAudit = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type={'stockAudit'}
reset={reset}
defaultValues={{ state: { type: 'new' } }}
defaultValues={{ state: { type: 'new' }, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
@ -64,7 +64,8 @@ const NewStockAudit = ({ onOk, reset }) => {
NewStockAudit.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewStockAudit

View File

@ -3,13 +3,14 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewCourierService = ({ onOk }) => {
const NewCourierService = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'courierService'}
defaultValues={{
active: true,
tracked: false
tracked: false,
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
@ -80,7 +81,8 @@ const NewCourierService = ({ onOk }) => {
NewCourierService.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewCourierService

View File

@ -3,9 +3,9 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewCourier = ({ onOk }) => {
const NewCourier = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'courier'}>
<NewObjectForm type={'courier'} defaultValues={{ ...defaultValues }}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
@ -74,7 +74,8 @@ const NewCourier = ({ onOk }) => {
NewCourier.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewCourier

View File

@ -3,11 +3,11 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewDocumentPrinter = ({ onOk }) => {
const NewDocumentPrinter = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'documentPrinter'}
defaultValues={{ active: true, global: false }}
defaultValues={{ active: true, global: false, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
@ -79,7 +79,8 @@ const NewDocumentPrinter = ({ onOk }) => {
NewDocumentPrinter.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewDocumentPrinter

View File

@ -3,9 +3,9 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewDocumentSize = ({ onOk }) => {
const NewDocumentSize = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'documentSize'}>
<NewObjectForm type={'documentSize'} defaultValues={{ ...defaultValues }}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
@ -60,7 +60,8 @@ const NewDocumentSize = ({ onOk }) => {
NewDocumentSize.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewDocumentSize

View File

@ -163,6 +163,7 @@ const DocumentTemplateInfo = () => {
content: false,
testObject: false
}}
labelWidth='175px'
/>
)
}}

View File

@ -3,11 +3,11 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewDocumentTemplate = ({ onOk }) => {
const NewDocumentTemplate = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'documentTemplate'}
defaultValues={{ active: true, global: false }}
defaultValues={{ active: true, global: false, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
@ -79,7 +79,8 @@ const NewDocumentTemplate = ({ onOk }) => {
NewDocumentTemplate.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewDocumentTemplate

View File

@ -3,11 +3,15 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewPart = ({ onOk }) => {
const NewPart = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'part'}
defaultValues={{ priceMode: 'margin', globalPricing: true }}
defaultValues={{
priceMode: 'margin',
globalPricing: true,
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
@ -112,7 +116,8 @@ const NewPart = ({ onOk }) => {
NewPart.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewPart

View File

@ -3,9 +3,12 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewProduct = ({ onOk }) => {
const NewProduct = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'product'} defaultValues={{ priceMode: 'margin' }}>
<NewObjectForm
type={'product'}
defaultValues={{ priceMode: 'margin', ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
@ -98,7 +101,8 @@ const NewProduct = ({ onOk }) => {
NewProduct.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewProduct

View File

@ -3,9 +3,9 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewTaxRate = ({ onOk }) => {
const NewTaxRate = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'taxRate'}>
<NewObjectForm type={'taxRate'} defaultValues={{ ...defaultValues }}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
@ -74,7 +74,8 @@ const NewTaxRate = ({ onOk }) => {
NewTaxRate.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewTaxRate

View File

@ -3,9 +3,9 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewTaxRecord = ({ onOk }) => {
const NewTaxRecord = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'taxRecord'}>
<NewObjectForm type={'taxRecord'} defaultValues={{ ...defaultValues }}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
@ -74,7 +74,8 @@ const NewTaxRecord = ({ onOk }) => {
NewTaxRecord.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewTaxRecord

View File

@ -3,9 +3,12 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewVendor = ({ onOk }) => {
const NewVendor = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'vendor'}>
<NewObjectForm
type={'vendor'}
defaultValues={{ active: true, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
@ -74,7 +77,8 @@ const NewVendor = ({ onOk }) => {
NewVendor.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewVendor

View File

@ -3,12 +3,13 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewGCodeFile = ({ onOk }) => {
const NewGCodeFile = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'gcodeFile'}
defaultValues={{
state: { type: 'draft' }
state: { type: 'draft' },
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
@ -83,7 +84,8 @@ const NewGCodeFile = ({ onOk }) => {
NewGCodeFile.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewGCodeFile

View File

@ -3,12 +3,13 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewJob = ({ onOk }) => {
const NewJob = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'job'}
defaultValues={{
state: { type: 'draft' }
state: { type: 'draft' },
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
@ -66,7 +67,8 @@ const NewJob = ({ onOk }) => {
NewJob.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewJob

View File

@ -3,7 +3,7 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewPrinter = ({ onOk }) => {
const NewPrinter = ({ onOk, defaultValues }) => {
return (
<NewObjectForm
type={'printer'}
@ -12,7 +12,8 @@ const NewPrinter = ({ onOk }) => {
port: 7125,
protocol: 'ws'
},
active: true
active: true,
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
@ -103,7 +104,8 @@ const NewPrinter = ({ onOk }) => {
NewPrinter.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewPrinter

View File

@ -54,7 +54,6 @@ const FileUpload = ({
currentFiles.length === 0
: !currentFiles
setHasNoItems(noItems)
console.log('No items', noItems)
}, [currentFiles, multiple])
const handleFileUpload = async (file) => {

View File

@ -11,6 +11,8 @@ const { Text, Link } = Typography
const IdDisplay = ({
id,
reference = null,
plainCode = false,
type,
showCopy = true,
longId = true,
@ -44,23 +46,37 @@ const IdDisplay = ({
displayId = prefix + ':' + id.toString().slice(-6)
}
if (reference) {
displayId = prefix + ':' + reference
}
return (
<Flex
align={'end'}
align={'center'}
className='iddisplay'
style={{ minWidth: '0px', width: '100%' }}
>
{(() => {
const textElement = (
<Text
code
ellipsis
var textElement = (
<code
style={showCopy ? { marginRight: 6, minWidth: '0px' } : undefined}
>
{displayId}
</Text>
</code>
)
if (plainCode == false) {
textElement = (
<Text
code
ellipsis
style={showCopy ? { marginRight: 6, minWidth: '0px' } : undefined}
>
{displayId}
</Text>
)
}
// If hyperlink is enabled
if (showHyperlink && hyperlink != null) {
const linkElement = (
@ -128,9 +144,11 @@ const IdDisplay = ({
IdDisplay.propTypes = {
id: PropTypes.string,
type: PropTypes.string,
reference: PropTypes.string,
showCopy: PropTypes.bool,
longId: PropTypes.bool,
showHyperlink: PropTypes.bool,
plainCode: PropTypes.bool,
showSpotlight: PropTypes.bool
}

View File

@ -259,7 +259,6 @@ const ObjectChildTable = ({
if (rollup && typeof rollup.value === 'function') {
try {
const updatedObjectData = { ...objectData }
console.log('UPDATED OBJECT DATA', value)
updatedObjectData[property.name] = value
summaryRow[property.name] = rollup.value(updatedObjectData)
} catch (e) {

View File

@ -1,24 +1,60 @@
import PropTypes from 'prop-types'
import { Typography, Flex, Badge, Tag } from 'antd'
import { useState, useEffect, useContext, useCallback } from 'react'
import { Typography, Flex, Badge, Tag, Popover } from 'antd'
import { useState, useEffect, useContext, useCallback, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { getModelByName } from '../../../database/ObjectModels'
import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext'
import merge from 'lodash/merge'
import IdDisplay from './IdDisplay'
import SpotlightTooltip from './SpotlightTooltip'
const { Text } = Typography
const { Text, Link } = Typography
const ObjectDisplay = ({ object, objectType }) => {
const ObjectDisplay = ({
object,
objectType,
showHyperlink = false,
showSpotlight = true
}) => {
const [objectData, setObjectData] = useState(object)
const { subscribeToObjectUpdates, connected } = useContext(ApiServerContext)
const { subscribeToObjectUpdates, connected, fetchSpotlightData } =
useContext(ApiServerContext)
const { token } = useContext(AuthContext)
const navigate = useNavigate()
const idRef = useRef(null)
// Update event handler
const updateObjectEventHandler = useCallback((value) => {
setObjectData((prev) => merge({}, prev, value))
}, [])
// Detect minimal objects that only contain an _id
const isMinimalObject = useCallback((obj) => {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false
const keys = Object.keys(obj)
return keys.length === 1 && keys[0] === '_id' && obj._id
}, [])
// If only an _id is provided, fetch the full object via spotlight
const fetchFullObjectIfNeeded = useCallback(
async (obj) => {
if (!isMinimalObject(obj) || !objectType) return obj
try {
const model = getModelByName(objectType)
const spotlightQuery = `${model.prefix}:${obj._id}`
const spotlightResult = await fetchSpotlightData(spotlightQuery)
if (spotlightResult && typeof spotlightResult === 'object') {
return spotlightResult
}
} catch (err) {
console.error('Failed to fetch spotlight data:', err)
}
return obj
},
[fetchSpotlightData, isMinimalObject, objectType]
)
// Subscribe to object updates when component mounts
useEffect(() => {
if (object?._id && objectType && connected && token) {
@ -43,46 +79,132 @@ const ObjectDisplay = ({ object, objectType }) => {
// Update local state when object prop changes
useEffect(() => {
setObjectData(object)
}, [object])
if (idRef.current == object?._id) return
idRef.current = object?._id
let cancelled = false
const hydrateObject = async () => {
const fullObject = await fetchFullObjectIfNeeded(object)
if (!cancelled) setObjectData(fullObject)
}
hydrateObject()
return () => {
cancelled = true
}
}, [object, fetchFullObjectIfNeeded])
console.log(objectData, objectType)
if (!objectData) {
return <Text type='secondary'>n/a</Text>
}
const model = getModelByName(objectType)
const Icon = model.icon
const prefix = model.prefix
// Get hyperlink URL from model's default actions
var hyperlink = null
const defaultModelActions =
model.actions?.filter((action) => action.default == true) || []
const objectId = objectData._id
if (defaultModelActions.length >= 1 && objectId) {
hyperlink = defaultModelActions[0].url(objectId)
}
// Render name with hyperlink/spotlight support
const renderNameDisplay = () => {
if (!objectData?.name) return null
const textElement = (
<Text ellipsis style={{ lineHeight: '1', paddingBottom: '2px' }}>
{objectData.name}
</Text>
)
// If hyperlink is enabled
if (showHyperlink && hyperlink != null) {
const linkElement = (
<Link onClick={() => navigate(hyperlink)} ellipsis>
{textElement}
</Link>
)
if (showSpotlight && objectId) {
return (
<Popover
content={
<SpotlightTooltip
query={prefix + ':' + objectId}
type={objectType}
/>
}
trigger={['hover', 'click']}
placement='topLeft'
arrow={false}
style={{ padding: 0 }}
>
{linkElement}
</Popover>
)
}
return linkElement
}
// If hyperlink is disabled
if (showSpotlight && objectId) {
return (
<Popover
content={
<SpotlightTooltip
query={prefix + ':' + objectId}
type={objectType}
/>
}
trigger={['hover', 'click']}
placement='topLeft'
arrow={false}
>
{textElement}
</Popover>
)
}
return textElement
}
return (
<Tag
style={{
margin: 0,
border: 'none',
minWidth: 0,
paddingBottom: '2.5px',
maxWidth: '100%'
}}
className='object-display-tag'
>
<Flex
gap={objectData?.color ? 'small' : '4px'}
gap={objectData?.color ? 'small' : '5px'}
align='center'
style={{ minWidth: 0 }}
style={{ minWidth: 0, height: '24px' }}
>
<Icon style={{ marginBottom: '-3px' }} />
<Icon />
<Flex gap={'small'} align='center' style={{ minWidth: 0 }}>
{objectData?.color ? <Badge color={objectData?.color} /> : null}
<div style={{ paddingTop: '1.5px', minWidth: 0 }}>
{objectData?.name ? (
<Text ellipsis style={{ lineHeight: '1' }}>
{objectData.name}
</Text>
) : null}
{objectData?.color ? (
<Badge
color={objectData?.color}
style={{ marginBottom: '1.5px' }}
/>
) : null}
<div style={{ minWidth: 0 }}>
{renderNameDisplay()}
{objectData?._id && !objectData?.name ? (
<IdDisplay
id={objectData?._id}
reference={objectData?._reference || undefined}
type={objectType}
longId={false}
showCopy={false}
showHyperlink={showHyperlink}
showSpotlight={showSpotlight}
/>
) : null}
</div>
@ -95,7 +217,9 @@ const ObjectDisplay = ({ object, objectType }) => {
ObjectDisplay.propTypes = {
object: PropTypes.object,
objectType: PropTypes.string,
style: PropTypes.object
style: PropTypes.object,
showHyperlink: PropTypes.bool,
showSpotlight: PropTypes.bool
}
export default ObjectDisplay

View File

@ -134,6 +134,10 @@ const ObjectForm = forwardRef(
return []
}
// Clone currentData to allow sequential updates
// We use this working copy to calculate subsequent dependent values
const workingData = merge({}, currentData)
const normalizedPath = (name, parentPath = []) => {
if (Array.isArray(name)) {
return [...parentPath, ...name]
@ -159,11 +163,17 @@ const ObjectForm = forwardRef(
const computedEntries = []
const processProperty = (property, scopeData, parentPath = []) => {
const processProperty = (property, parentPath = []) => {
if (!property?.name) return
const propertyPath = normalizedPath(property.name, parentPath)
// Determine the scope data for calculation (parent object)
const scopeData =
parentPath.length === 0
? workingData
: getValueAtPath(workingData, parentPath)
if (property.value && typeof property.value === 'function') {
try {
const computedValue = property.value(scopeData || {})
@ -172,6 +182,8 @@ const ObjectForm = forwardRef(
namePath: propertyPath,
value: computedValue
})
// Update workingData so subsequent properties can use this value
set(workingData, propertyPath, computedValue)
}
} catch (error) {
console.warn(
@ -186,29 +198,25 @@ const ObjectForm = forwardRef(
property.properties.length > 0
) {
if (property.type === 'objectChildren') {
const childValues = getValueAtPath(currentData, propertyPath)
// Use workingData to get the latest state of children
const childValues = getValueAtPath(workingData, propertyPath)
if (Array.isArray(childValues)) {
childValues.forEach((childData = {}, index) => {
childValues.forEach((_, index) => {
property.properties.forEach((childProperty) => {
processProperty(childProperty, childData || {}, [
...propertyPath,
index
])
processProperty(childProperty, [...propertyPath, index])
})
})
}
} else {
const nestedScope =
getValueAtPath(currentData, propertyPath) || {}
property.properties.forEach((childProperty) => {
processProperty(childProperty, nestedScope || {}, propertyPath)
processProperty(childProperty, propertyPath)
})
}
}
}
modelDefinition.properties.forEach((property) => {
processProperty(property, currentData)
processProperty(property, [])
})
return computedEntries
@ -294,7 +302,17 @@ const ObjectForm = forwardRef(
fetchObject
)
}
}, [fetchObject, fetchObjectLock, id, type, form, messageApi, showError])
}, [
fetchObject,
fetchObjectLock,
id,
type,
form,
messageApi,
showError,
calculateComputedValues,
model
])
// Update event handler
const updateObjectEventHandler = useCallback((value) => {
@ -354,7 +372,6 @@ const ObjectForm = forwardRef(
const startEditing = () => {
setIsEditing(true)
isEditingRef.current = true
console.log('IS EDITING TRUE')
setObjectData((prev) => ({ ...prev, _isEditing: isEditingRef.current }))
onStateChangeRef.current({
isEditing: true,
@ -379,7 +396,6 @@ const ObjectForm = forwardRef(
setIsEditing(false)
isEditingRef.current = false
form.setFieldsValue(resetFormData)
console.log('IS EDITING FALSE')
setObjectData({ ...resetFormData, _isEditing: isEditingRef.current })
}
@ -486,7 +502,7 @@ const ObjectForm = forwardRef(
// Calculate computed values based on current form data
const currentFormData = {
...(serverObjectData.current || {}),
...allFormValues
...changedValues
}
const computedEntries = calculateComputedValues(
currentFormData,
@ -519,7 +535,7 @@ const ObjectForm = forwardRef(
mergedFormValues._isEditing = isEditingRef.current
setObjectData((prev) => {
return { ...prev, ...mergedFormValues }
return merge({}, prev, mergedFormValues)
})
}}
>

View File

@ -131,6 +131,10 @@ const ObjectProperty = ({
options = options(objectData)
}
if (readOnly && typeof readOnly == 'function' && objectData) {
readOnly = readOnly(objectData)
}
if (!value) {
value = getPropertyValue(objectData, name)
}
@ -364,7 +368,13 @@ const ObjectProperty = ({
}
case 'object': {
if (value && value._id) {
return <ObjectDisplay object={value} objectType={objectType} />
return (
<ObjectDisplay
object={value}
objectType={objectType}
showHyperlink={showHyperlink}
/>
)
} else {
return (
<Text type='secondary' {...textParams}>
@ -439,6 +449,24 @@ const ObjectProperty = ({
)
}
}
case 'reference': {
if (value) {
return (
<IdDisplay
id={value}
reference={value}
type={objectType}
{...rest}
/>
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'miscId': {
return <MiscId value={value} {...rest} />
}

View File

@ -47,11 +47,23 @@ const ObjectSelect = ({
const [objectList, setObjectList] = useState([])
const [treeSelectValue, setTreeSelectValue] = useState(null)
const [initialLoading, setInitialLoading] = useState(true)
const valueRef = useRef(null)
// Refs to track value changes
const prevValueRef = useRef(value)
const isInternalChangeRef = useRef(false)
// Normalize a value to an identity string so we can detect in-place _id updates
const getValueIdentity = useCallback((val) => {
if (val && typeof val === 'object') {
if (val._id) return String(val._id)
if (val.value && typeof val.value === 'object' && val.value._id)
return String(val.value._id)
}
return JSON.stringify(val)
}, [])
const prevValueIdentityRef = useRef(getValueIdentity(value))
// Utility function to check if object only contains _id
const isMinimalObject = useCallback((obj) => {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
@ -162,7 +174,6 @@ const ObjectSelect = ({
const buildTreeData = useCallback(
(data, pIdx = 0, parentKeys = [], filterPath = []) => {
if (!data || !Array.isArray(data)) return []
console.log(data, pIdx, properties.length)
// If we are past the grouping properties, these are leaf objects
if (pIdx >= properties.length) {
return data.map((object) => {
@ -180,6 +191,7 @@ const ObjectSelect = ({
objectType={type}
objectData={object}
isEditing={false}
style={{ top: '-0.5px' }}
/>
</div>
),
@ -342,12 +354,14 @@ const ObjectSelect = ({
value &&
typeof value === 'object' &&
value !== null &&
!initialized &&
valueRef.current !== value &&
type != 'unknown'
) {
console.log('fetching full object', value)
valueRef.current = value
// Check if value is a minimal object and fetch full object if needed
const fullValue = await fetchFullObjectIfNeeded(value)
console.log('fullValue', fullValue)
// Build a new filter from value's properties that are in the properties list
const valueFilter = { ...filter }
properties.forEach((prop) => {
@ -370,7 +384,8 @@ const ObjectSelect = ({
})
// Fetch with the new filter
handleFetchObjectsProperties(valueFilter)
setTreeSelectValue(fullValue._id)
console.log('setting treeSelectValue', valueRef.current._id)
setTreeSelectValue(valueRef.current._id)
setInitialized(true)
return
}
@ -410,6 +425,10 @@ const ObjectSelect = ({
const prevValuesRef = useRef({ type, masterFilter })
useEffect(() => {
console.log('treeSelectValue', treeSelectValue)
}, [treeSelectValue])
useEffect(() => {
const prevValues = prevValuesRef.current
@ -433,8 +452,9 @@ const ObjectSelect = ({
useEffect(() => {
// Check if value has actually changed
const currentValueIdentity = getValueIdentity(value)
const hasValueChanged =
JSON.stringify(prevValueRef.current) !== JSON.stringify(value)
prevValueIdentityRef.current !== currentValueIdentity
if (hasValueChanged) {
const changeSource = isInternalChangeRef.current ? 'internal' : 'external'
@ -451,8 +471,9 @@ const ObjectSelect = ({
// Update the previous value reference
prevValueRef.current = value
prevValueIdentityRef.current = currentValueIdentity
}
}, [value])
}, [value, getValueIdentity])
const placeholder = useMemo(
() =>

View File

@ -88,8 +88,8 @@ const ObjectTable = forwardRef(
const [, contextHolder] = message.useMessage()
const tableRef = useRef(null)
const model = getModelByName(type)
const [tableFilter, setTableFilter] = useState({})
const [tableSorter, setTableSorter] = useState({})
const tableFilterRef = useRef({})
const tableSorterRef = useRef({})
const [initialized, setInitialized] = useState(false)
// Table state
@ -98,6 +98,7 @@ const ObjectTable = forwardRef(
const [hasMore, setHasMore] = useState(true)
const [loading, setLoading] = useState(true)
const [lazyLoading, setLazyLoading] = useState(false)
const [tableData, setTableData] = useState([])
const subscribedIdsRef = useRef([])
// const [typeSubscribed, setTypeSubscribed] = useState(false)
@ -159,17 +160,17 @@ const ObjectTable = forwardRef(
const fetchData = useCallback(
async (pageNum = 1, filter = null, sorter = null) => {
if (filter == null) {
filter = tableFilter
filter = tableFilterRef.current
} else {
setTableFilter(filter)
tableFilterRef.current = filter
}
if (sorter == null) {
sorter = tableSorter
sorter = tableSorterRef.current
} else {
setTableSorter({
tableSorterRef.current = {
field: sorter.field,
order: sorter.order
})
}
}
console.log('Fetching data...')
try {
@ -213,15 +214,7 @@ const ObjectTable = forwardRef(
throw error
}
},
[
type,
masterFilter,
pageSize,
tableFilter,
tableSorter,
onDataChange,
fetchObjects
]
[type, masterFilter, pageSize, onDataChange, fetchObjects]
)
const loadNextPage = useCallback(() => {
@ -480,8 +473,8 @@ const ObjectTable = forwardRef(
if (hasChanged) {
setPages([])
setTableFilter({})
setTableSorter({})
tableFilterRef.current = {}
tableSorterRef.current = {}
setInitialized(false)
setLoading(true)
setLazyLoading(false)
@ -560,6 +553,12 @@ const ObjectTable = forwardRef(
useEffect(() => {
pagesRef.current = pages
}, [pages])
// Flatten pages array for table display
useEffect(() => {
setTableData(pages.flatMap((page) => page.items))
}, [pages])
// Add columns in the order specified by model.columns
model.columns.forEach((colName) => {
const prop = modelProperties.find((p) => p.name === colName)
@ -601,9 +600,8 @@ const ObjectTable = forwardRef(
const isSortable = model.sorters && model.sorters.includes(prop.name)
const columnConfig = {
sorter: isSortable,
sorter: isSortable ? { multiple: 1 } : undefined,
title: prop.label,
dataIndex: prop.name,
width: prop.columnWidth || width,
fixed: isMobile ? undefined : fixed,
key: prop.name,
@ -663,9 +661,6 @@ const ObjectTable = forwardRef(
})
}
// Flatten pages array for table display
const tableData = pages.flatMap((page) => page.items)
// Card view rendering
const cardsContainerRef = useRef(null)

View File

@ -24,7 +24,9 @@ const ObjectTypeSelect = ({
})
.map((model) => ({
value: model.name,
label: <ObjectTypeDisplay objectType={model.name} />,
label: (
<ObjectTypeDisplay objectType={model.name} style={{ top: '-0.5px' }} />
),
searchText: model.label?.toLowerCase() || ''
}))

View File

@ -37,13 +37,11 @@ const PrinterMiscPanel = ({ id, showControls = true }) => {
useEffect(() => {
if (id && connected == true) {
console.log('subscribing to misc event')
const miscEventUnsubscribe = subscribeToObjectEvent(
id,
'printer',
'misc',
(event) => {
console.log('misc event', event)
setMiscData((prev) => {
const merged = merge({}, prev, event.data)
return merged

View File

@ -55,13 +55,11 @@ const PrinterPositionPanel = ({
useEffect(() => {
if (id && connected == true) {
console.log('subscribing to motion event')
const motionEventUnsubscribe = subscribeToObjectEvent(
id,
'printer',
'motion',
(event) => {
console.log('motion event', event)
setPositionData((prev) => {
const merged = merge({}, prev, event.data)
return merged

View File

@ -101,9 +101,7 @@ const PrinterTemperaturePanel = ({
type: 'setTemperature',
data
},
(result) => {
console.log('setTemperatureResult', result)
}
() => {}
)
}
}

View File

@ -90,10 +90,7 @@ const ActionsModalProvider = ({ children }) => {
// Use a small timeout to ensure the modal is fully rendered and visible
setTimeout(() => {
if (visible) {
console.log('visible', visible)
console.log('inputRef.current', inputRef.current)
if (visible && inputRef.current) {
console.log('focusing input')
const input = inputRef.current.input
if (input) {
input.focus()

View File

@ -89,8 +89,7 @@ const ApiServerProvider = ({ children }) => {
newSocket.on('connect', () => {
logger.debug('Api Server connected')
newSocket.emit('authenticate', { token: token }, (result) => {
console.log('Auth result', result)
newSocket.emit('authenticate', { token: token }, () => {
setConnecting(false)
setConnected(true)
setError(null)
@ -328,11 +327,6 @@ const ApiServerProvider = ({ children }) => {
const offObjectTypeUpdatesEvent = useCallback((objectType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
// Remove callback from the subscribed callbacks map
console.log(
'Unsubscribing from type',
objectType,
subscribedCallbacksRef.current.has(objectType)
)
if (subscribedCallbacksRef.current.has(objectType)) {
const callbacks = subscribedCallbacksRef.current
.get(objectType)
@ -453,7 +447,6 @@ const ApiServerProvider = ({ children }) => {
.filter((cb) => cb !== callback)
if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(callbacksRefKey)
console.log('Unsubscribing from object event:', callbacksRefKey)
socketRef.current.emit('unsubscribeObjectEvent', {
_id: id,
objectType,
@ -481,7 +474,6 @@ const ApiServerProvider = ({ children }) => {
subscribedCallbacksRef.current.get(callbacksRefKey).length
if (callbacksLength <= 0) {
console.log('Subscribing to object event:', callbacksRefKey)
socketRef.current.emit(
'subscribeToObjectEvent',
{

View File

@ -0,0 +1,6 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/orderitemicon.svg?react'
const OrderItemIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default OrderItemIcon

View File

@ -1,6 +0,0 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/orderitemsicon.svg?react'
const OrderItemsIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default OrderItemsIcon

View File

@ -0,0 +1,6 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/shipmenticon.svg?react'
const ShipmentIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default ShipmentIcon

View File

@ -18,6 +18,8 @@ import { StockAudit } from './models/StockAudit'
import { PartStock } from './models/PartStock'
import { ProductStock } from './models/ProductStock'
import { PurchaseOrder } from './models/PurchaseOrder'
import { OrderItem } from './models/OrderItem'
import { Shipment } from './models/Shipment'
import { AuditLog } from './models/AuditLog'
import { User } from './models/User'
import { NoteType } from './models/NoteType'
@ -51,6 +53,8 @@ export const objectModels = [
PartStock,
ProductStock,
PurchaseOrder,
OrderItem,
Shipment,
AuditLog,
User,
NoteType,
@ -85,6 +89,8 @@ export {
PartStock,
ProductStock,
PurchaseOrder,
OrderItem,
Shipment,
AuditLog,
User,
NoteType,

View File

@ -9,13 +9,12 @@ export const AuditLog = {
columns: [
'_id',
'owner',
'owner._id',
'parent._id',
'parent',
'operation',
'changes',
'createdAt'
],
filters: ['_id', 'owner._id', 'parent._id', 'operation'],
filters: ['_id', 'owner', 'parent', 'operation'],
sorters: ['createdAt'],
properties: [
{
@ -50,18 +49,8 @@ export const AuditLog = {
},
columnFixed: 'left',
value: null,
showCopy: true
},
{
name: 'owner._id',
label: 'Owner ID',
type: 'id',
objectType: (objectData) => {
return objectData.ownerType
},
columnFixed: 'left',
showHyperlink: true,
showCopy: true
showCopy: true,
showHyperlink: true
},
{
name: 'parent',
@ -71,17 +60,8 @@ export const AuditLog = {
return objectData.parentType
},
value: null,
showCopy: true
},
{
name: 'parent._id',
label: 'Parent ID',
type: 'id',
objectType: (objectData) => {
return objectData.parentType
},
showHyperlink: true,
showCopy: true
showCopy: true,
showHyperlink: true
},
{
name: 'operation',

View File

@ -74,15 +74,14 @@ export const CourierService = {
'name',
'_id',
'courier',
'courier._id',
'tracked',
'deliveryTime',
'active'
],
filters: ['name', '_id', 'courier._id', 'active', 'deliveryTime', 'tracked'],
filters: ['name', '_id', 'courier', 'active', 'deliveryTime', 'tracked'],
sorters: [
'name',
'courier._id',
'courier',
'active',
'tracked',
'estimatedDeliveryTime',
@ -126,13 +125,6 @@ export const CourierService = {
showHyperlink: true,
required: true
},
{
name: 'courier._id',
label: 'Courier ID',
type: 'id',
objectType: 'courier',
showHyperlink: true
},
{
name: 'deliveryTime',
label: 'Delivery Time',

View File

@ -121,16 +121,8 @@ export const DocumentJob = {
type: 'object',
objectType: (objectData) => {
return objectData?.objectType
}
},
{
name: 'object._id',
label: 'Object ID',
type: 'id',
showHyperlink: true,
objectType: (objectData) => {
return objectData?.objectType
}
},
showHyperlink: true
},
{
name: 'documentTemplate',
@ -139,6 +131,7 @@ export const DocumentJob = {
columnWidth: 150,
type: 'object',
objectType: 'documentTemplate',
showHyperlink: true,
masterFilter: (objectData) => {
return {
active: true,
@ -147,13 +140,6 @@ export const DocumentJob = {
}
}
},
{
name: 'documentTemplate._id',
label: 'Template ID',
type: 'id',
showHyperlink: true,
objectType: 'documentTemplate'
},
{
name: 'documentPrinter',
label: 'Printer',
@ -161,19 +147,13 @@ export const DocumentJob = {
columnWidth: 150,
type: 'object',
objectType: 'documentPrinter',
showHyperlink: true,
masterFilter: () => {
return {
active: true,
online: true
}
}
},
{
name: 'documentPrinter._id',
label: 'Printer ID',
type: 'id',
showHyperlink: true,
objectType: 'documentPrinter'
}
]
}

View File

@ -66,7 +66,6 @@ export const DocumentPrinter = {
'_id',
'state',
'host',
'host._id',
'tags',
'connectedAt',
'updatedAt'
@ -132,15 +131,8 @@ export const DocumentPrinter = {
label: 'Vendor',
type: 'object',
objectType: 'vendor',
required: false
},
{
name: 'vendor._id',
label: 'Vendor ID',
type: 'id',
objectType: 'vendor',
showHyperlink: true,
readOnly: true
required: false,
showHyperlink: true
},
{
name: 'host',
@ -150,14 +142,6 @@ export const DocumentPrinter = {
objectType: 'host',
showHyperlink: true
},
{
name: 'host._id',
label: 'Host ID',
type: 'id',
objectType: 'host',
showCopy: true,
showHyperlink: true
},
{
name: 'connection.interface',
label: 'Interface',
@ -211,14 +195,7 @@ export const DocumentPrinter = {
label: 'Current Document Size',
required: false,
type: 'object',
objectType: 'documentSize'
},
{
name: 'currentDocumentSize._id',
label: 'Current Document Size ID',
type: 'id',
objectType: 'documentSize',
showCopy: true,
showHyperlink: true
},
{

View File

@ -78,9 +78,7 @@ export const DocumentTemplate = {
'objectType',
'tags',
'parent',
'parent._id',
'documentSize',
'documentSize._id',
'createdAt',
'updatedAt'
],
@ -158,14 +156,7 @@ export const DocumentTemplate = {
label: 'Document Size',
required: true,
type: 'object',
objectType: 'documentSize'
},
{
name: 'documentSize._id',
label: 'Document Size ID',
type: 'id',
objectType: 'documentSize',
showCopy: true,
showHyperlink: true
},
{
@ -175,17 +166,7 @@ export const DocumentTemplate = {
type: 'object',
masterFilter: { global: true, active: true },
objectType: 'documentTemplate',
empty: (documentTemplate) => {
return documentTemplate.global
}
},
{
name: 'parent._id',
label: 'Parent ID',
required: false,
type: 'id',
showHyperlink: true,
objectType: 'documentTemplate',
empty: (documentTemplate) => {
return documentTemplate.global
}

View File

@ -65,14 +65,13 @@ export const Filament = {
'type',
'color',
'vendor',
'vendor._id',
'cost',
'density',
'diameter',
'createdAt',
'updatedAt'
],
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor', 'vendor._id'],
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor'],
sorters: [
'name',
'createdAt',
@ -116,14 +115,7 @@ export const Filament = {
label: 'Vendor',
required: true,
type: 'object',
objectType: 'vendor'
},
{
name: 'vendor._id',
label: 'Vendor ID',
type: 'id',
objectType: 'vendor',
showCopy: true,
showHyperlink: true
},
{

View File

@ -23,7 +23,6 @@ export const FilamentStock = {
'currentWeight',
'startingWeight',
'filament',
'filament._id',
'createdAt',
'updatedAt'
],
@ -65,15 +64,8 @@ export const FilamentStock = {
objectType: 'filament',
readOnly: true,
initial: true,
required: true
},
{
name: 'filament._id',
label: 'Filament ID',
type: 'id',
objectType: 'filament',
showHyperlink: true,
readOnly: true
required: true,
showHyperlink: true
},
{
name: 'currentWeight',

View File

@ -122,31 +122,16 @@ export const GCodeFile = {
value: null,
required: true,
showPreview: false,
showHyperlink: false,
showHyperlink: true,
filter: ['.gcode', '.g']
},
{
name: 'file._id',
label: 'File ID',
type: 'id',
value: null,
objectType: 'file',
showHyperlink: true
},
{
name: 'filament',
label: 'Filament',
type: 'object',
value: null,
objectType: 'filament',
required: true
},
{
name: 'filament._id',
label: 'Filament ID',
type: 'id',
value: null,
objectType: 'filament',
required: true,
showHyperlink: true
},
{
@ -236,17 +221,8 @@ export const GCodeFile = {
label: 'Part',
type: 'object',
objectType: 'part',
required: true
},
{
name: 'part._id',
label: 'Part ID',
type: 'id',
objectType: 'part',
showHyperlink: true,
value: (objectData) => {
return objectData?.part?._id
}
required: true,
showHyperlink: true
},
{
name: 'quantity',

View File

@ -40,13 +40,12 @@ export const Job = {
columns: [
'_id',
'gcodeFile',
'gcodeFile._id',
'quantity',
'state',
'createdAt'
],
filters: ['state', '_id', 'gcodeFile._id', 'quantity'],
filters: ['state', '_id', 'gcodeFile', 'quantity'],
sorters: ['createdAt', 'state', 'quantity', 'gcodeFile'],
properties: [
{
@ -113,13 +112,7 @@ export const Job = {
type: 'object',
columnFixed: 'left',
objectType: 'gcodeFile',
required: true
},
{
name: 'gcodeFile._id',
label: 'GCode File ID',
type: 'id',
objectType: 'gcodeFile',
required: true,
showHyperlink: true
}
]

View File

@ -31,9 +31,30 @@ export const Note = {
readOnly: true
},
{
name: 'parent._id',
label: 'Parent ID',
type: 'id',
name: '_reference',
label: 'Reference',
type: 'reference',
objectType: 'note',
showCopy: true,
readOnly: true
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true
},
{
name: 'user',
label: 'User',
type: 'object',
objectType: 'user',
showHyperlink: true
},
{
name: 'parent',
label: 'Parent',
type: 'object',
objectType: (objectData) => {
return objectData.parentType
},
@ -54,24 +75,6 @@ export const Note = {
objectType: 'noteType',
showHyperlink: true,
required: true
},
{
name: 'noteType._id',
label: 'Note Type ID',
type: 'id',
objectType: 'noteType'
},
{
name: 'user._id',
label: 'User ID',
type: 'id',
objectType: 'user'
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true
}
]
}

View File

@ -0,0 +1,214 @@
import OrderItemIcon from '../../components/Icons/OrderItemIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const OrderItem = {
name: 'orderItem',
label: 'Order Item',
prefix: 'ODI',
icon: OrderItemIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) => `/dashboard/inventory/orderitems/info?orderItemId=${_id}`
}
],
group: [],
filters: ['itemType', 'item', 'order'],
sorters: ['createdAt', 'updatedAt', 'itemAmount', 'quantity'],
columns: [
'_id',
'itemType',
'item',
'itemAmount',
'quantity',
'totalAmount',
'taxRate',
'totalAmountWithTax',
'order',
'createdAt',
'updatedAt'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
columnFixed: 'left',
objectType: 'orderItem',
columnWidth: 140,
showCopy: true
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true
},
{
name: 'orderType',
label: 'Order Type',
type: 'objectType',
masterFilter: ['purchaseOrder', 'salesOrder'],
required: true
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true
},
{
name: 'order',
label: 'Order',
type: 'object',
showHyperlink: true,
objectType: (objectData) => {
return objectData?.orderType
},
required: true
},
{
name: 'itemType',
label: 'Item Type',
type: 'objectType',
masterFilter: ['part', 'packaging'],
required: true,
columnWidth: 125
},
{
name: 'item',
label: 'Item',
type: 'object',
objectType: (objectData) => {
return objectData?.itemType
},
required: true,
showHyperlink: true,
columnWidth: 300
},
{
name: 'syncAmount',
label: 'Sync Amount',
type: 'select',
options: [
{ label: 'Item Cost', value: 'itemCost' },
{ label: 'Item Price', value: 'itemPrice' },
{ label: 'None', value: null }
],
required: false
},
{
name: 'itemAmount',
label: 'Item Amount',
type: 'number',
required: true,
prefix: '£',
min: 0,
step: 0.01,
readOnly: (objectData) => {
return objectData?.syncAmount != null
},
columnWidth: 150,
value: (objectData) => {
if (objectData?.item?.cost && objectData?.syncAmount == 'itemCost') {
return objectData?.item?.cost || undefined
}
if (objectData?.item?.price && objectData?.syncAmount == 'itemPrice') {
return objectData?.item?.price || undefined
}
return objectData?.itemAmount || undefined
}
},
{
name: 'quantity',
label: 'Quantity',
type: 'number',
required: true,
columnWidth: 150
},
{
name: 'totalAmount',
label: 'Total Amount',
type: 'number',
required: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 150,
readOnly: true,
value: (objectData) => {
if (objectData?.itemAmount && objectData?.quantity) {
return (
(objectData?.itemAmount || 0) * (objectData?.quantity || 0)
).toFixed(2)
} else {
return 0
}
}
},
{
name: 'taxRate',
label: 'Tax Rate',
type: 'object',
objectType: 'taxRate',
showHyperlink: true,
value: (objectData) => {
if (
objectData?.item?.costTaxRate?._id &&
objectData?.syncAmount == 'itemCost'
) {
return objectData?.item?.costTaxRate || undefined
} else if (
objectData?.item?.priceTaxRate?._id &&
objectData?.syncAmount == 'itemPrice'
) {
return objectData?.item?.priceTaxRate || undefined
} else {
return objectData?.taxRate || undefined
}
},
readOnly: (objectData) => {
return objectData?.syncAmount != null
}
},
{
name: 'totalAmountWithTax',
label: 'Total Amount w/ Tax',
type: 'number',
required: true,
readOnly: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 175,
value: (objectData) => {
const totalAmount = objectData?.itemAmount * objectData?.quantity || 0
if (objectData?.taxRate?.rateType == 'percentage') {
if (objectData?.quantity == undefined || objectData?.quantity == 0) {
return undefined
}
return (
(
(totalAmount || 0) *
(1 + objectData?.taxRate?.rate / 100)
).toFixed(2) || undefined
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
((totalAmount || 0) + objectData?.taxRate?.rate).toFixed(2) ||
undefined
)
} else {
return totalAmount || 0
}
}
}
]
}

View File

@ -58,15 +58,8 @@ export const Part = {
}
}
],
columns: [
'name',
'_id',
'product',
'product._id',
'globalPricing',
'createdAt'
],
filters: ['name', '_id', 'product', 'product._id', 'globalPricing'],
columns: ['name', '_id', 'product', 'globalPricing', 'createdAt'],
filters: ['name', '_id', 'product', 'globalPricing'],
sorters: ['name', 'email', 'role', 'createdAt', '_id'],
properties: [
{
@ -103,6 +96,7 @@ export const Part = {
required: true,
type: 'object',
objectType: 'vendor',
showHyperlink: true,
value: (objectData) => {
console.log(objectData?.vendor, objectData?.product?.vendor)
if (!objectData?.vendor && objectData?.product?.vendor) {
@ -112,14 +106,6 @@ export const Part = {
}
}
},
{
name: 'vendor._id',
label: 'Vendor ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'vendor'
},
{
name: 'cost',
label: 'Cost',
@ -132,7 +118,7 @@ export const Part = {
},
{
name: 'costWithTax',
label: 'Cost with Tax',
label: 'Cost w/ Tax',
columnWidth: 150,
required: true,
readOnly: true,
@ -163,15 +149,8 @@ export const Part = {
label: 'Cost Tax Rate',
required: true,
type: 'object',
objectType: 'taxRate'
},
{
name: 'costTaxRate._id',
label: 'Cost Tax Rate ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'taxRate'
objectType: 'taxRate',
showHyperlink: true
},
{
name: 'price',
@ -201,7 +180,7 @@ export const Part = {
},
{
name: 'priceWithTax',
label: 'Price with Tax',
label: 'Price w/ Tax',
columnWidth: 150,
required: true,
readOnly: true,
@ -251,15 +230,8 @@ export const Part = {
label: 'Price Tax Rate',
required: true,
type: 'object',
objectType: 'taxRate'
},
{
name: 'priceTaxRate._id',
label: 'Price Tax Rate ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'taxRate'
objectType: 'taxRate',
showHyperlink: true
},
{
@ -267,14 +239,7 @@ export const Part = {
label: 'File',
type: 'file',
value: null,
required: false
},
{
name: 'file._id',
label: 'File ID',
type: 'id',
value: null,
objectType: 'file',
required: false,
showHyperlink: true
}
]

View File

@ -25,7 +25,6 @@ export const PartStock = {
'startingQuantity',
'currentQuantity',
'part',
'part._id',
'createdAt',
'updatedAt'
],
@ -66,26 +65,12 @@ export const PartStock = {
required: true,
masterFilter: ['subJob']
},
{
name: 'consumedAt',
label: 'Consumed At',
type: 'dateTime',
readOnly: true
},
{
name: 'part',
label: 'Part',
type: 'object',
objectType: 'part',
required: true
},
{
name: 'part._id',
label: 'Part ID',
type: 'id',
objectType: 'part',
readOnly: true,
required: true,
showHyperlink: true
},
@ -98,17 +83,8 @@ export const PartStock = {
columnWidth: 200,
objectType: (objectData) => {
return objectData?.sourceType
}
},
{
name: 'source._id',
label: 'Source ID',
type: 'id',
readOnly: true,
columnWidth: 200,
objectType: (objectData) => {
return objectData?.sourceType
}
},
showHyperlink: true
},
{
name: 'currentQuantity',
@ -124,13 +100,6 @@ export const PartStock = {
return objectData.currentQuantity
}
}
},
{
name: 'startingQuantity',
label: 'Starting Quantity',
type: 'number',
columnWidth: 200,
required: true
}
]
}

View File

@ -213,16 +213,7 @@ export const Printer = {
]
}
],
columns: [
'name',
'_id',
'state',
'host',
'host._id',
'tags',
'connectedAt',
'updatedAt'
],
columns: ['name', '_id', 'state', 'host', 'tags', 'connectedAt', 'updatedAt'],
filters: ['name', '_id', 'state', 'tags'],
sorters: ['name', 'state', 'connectedAt'],
group: ['tags'],
@ -286,30 +277,16 @@ export const Printer = {
label: 'Vendor',
type: 'object',
objectType: 'vendor',
required: false
},
{
name: 'vendor._id',
label: 'Vendor ID',
type: 'id',
objectType: 'vendor',
showHyperlink: true,
readOnly: true
required: false,
showHyperlink: true
},
{
name: 'host',
label: 'Host',
type: 'object',
objectType: 'host',
required: true
},
{
name: 'host._id',
label: 'Host ID',
type: 'id',
objectType: 'host',
showHyperlink: true,
readOnly: true
required: true,
showHyperlink: true
},
{
name: 'moonraker.host',
@ -354,45 +331,24 @@ export const Printer = {
label: 'Filament Stock',
type: 'object',
objectType: 'filamentStock',
required: false
},
{
name: 'currentFilamentStock._id',
label: 'Filament Stock ID',
type: 'id',
objectType: 'filamentStock',
showHyperlink: true,
readOnly: true
required: false,
showHyperlink: true
},
{
name: 'currentJob',
label: 'Current Job',
type: 'object',
objectType: 'job',
required: false
},
{
name: 'currentJob._id',
label: 'Current Job ID',
type: 'id',
objectType: 'job',
showHyperlink: true,
readOnly: true
required: false,
showHyperlink: true
},
{
name: 'currentSubJob',
label: 'Current Sub Job',
type: 'object',
objectType: 'subJob',
required: false
},
{
name: 'currentSubJob._id',
label: 'Current Sub Job ID',
type: 'id',
objectType: 'subJob',
showHyperlink: true,
readOnly: true
required: false,
showHyperlink: true
},
{
name: 'alerts',

View File

@ -64,12 +64,11 @@ export const Product = {
'name',
'tags',
'vendor',
'vendor._id',
'price',
'createdAt',
'updatedAt'
],
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor', 'vendor._id'],
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor'],
sorters: ['name', 'createdAt', 'type', 'vendor', 'cost', 'updatedAt'],
properties: [
{
@ -103,15 +102,8 @@ export const Product = {
label: 'Vendor',
required: true,
type: 'object',
objectType: 'vendor'
},
{
name: 'vendor._id',
label: 'Vendor ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'vendor'
objectType: 'vendor',
showHyperlink: true
},
{
name: 'version',
@ -167,17 +159,8 @@ export const Product = {
label: 'Part',
type: 'object',
objectType: 'part',
required: true
},
{
name: 'part._id',
label: 'Part ID',
type: 'id',
objectType: 'part',
showHyperlink: true,
value: (objectData) => {
return objectData?.part?._id
}
required: true,
showHyperlink: true
},
{
name: 'quantity',

View File

@ -1,5 +1,6 @@
import PurchaseOrderIcon from '../../components/Icons/PurchaseOrderIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
export const PurchaseOrder = {
name: 'purchaseOrder',
@ -15,12 +16,20 @@ export const PurchaseOrder = {
icon: InfoCircleIcon,
url: (_id) =>
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}`
},
{
name: 'New Order Item',
label: 'New Order Item',
type: 'button',
icon: PlusIcon,
url: (_id) =>
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=newOrderItem`
}
],
group: ['vendor'],
filters: ['vendor', 'vendor._id'],
filters: ['vendor'],
sorters: ['createdAt', 'state', 'updatedAt'],
columns: ['_id', 'createdAt', 'state', 'updatedAt', 'vendor', 'vendor._id'],
columns: ['_id', 'createdAt', 'state', 'updatedAt', 'vendor'],
properties: [
{
name: '_id',
@ -49,183 +58,8 @@ export const PurchaseOrder = {
label: 'Vendor',
required: true,
type: 'object',
objectType: 'vendor'
},
{
name: 'vendor._id',
label: 'Vendor ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'vendor'
},
{
name: 'items',
label: 'Order Items',
type: 'objectChildren',
required: true,
properties: [
{
name: 'itemType',
label: 'Item Type',
type: 'objectType',
masterFilter: ['part', 'packaging'],
required: true
},
{
name: 'item',
label: 'Item',
type: 'object',
objectType: (objectData) => {
return objectData?.itemType
},
required: true
},
{
name: 'item._id',
label: 'Item ID',
type: 'id',
objectType: (objectData) => {
return objectData?.itemType
},
showHyperlink: true,
value: (objectData) => {
return objectData?.item?._id
}
},
{
name: 'itemCost',
label: 'Item Cost',
type: 'number',
required: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 150,
value: (objectData) => {
if (objectData?.item) {
return objectData?.item?.cost || undefined
} else {
return undefined
}
}
},
{
name: 'quantity',
label: 'Quantity',
type: 'number',
required: true,
columnWidth: 150
},
{
name: 'totalCost',
label: 'Total Cost',
type: 'number',
required: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 150,
value: (objectData) => {
return (
(objectData?.itemCost || 0) * (objectData?.quantity || 0) ||
undefined
)
}
},
{
name: 'taxRate',
label: 'Tax Rate',
type: 'object',
objectType: 'taxRate',
value: (objectData) => {
if (objectData?.item) {
console.log(objectData?.item)
return objectData?.item?.costTaxRate || undefined
} else {
return undefined
}
}
},
{
name: 'taxRate._id',
label: 'Tax Rate ID',
type: 'id',
showHyperlink: true,
objectType: 'taxRate',
value: (objectData) => {
return objectData?.taxRate?._id || undefined
}
},
{
name: 'totalCostWithTax',
label: 'Total Cost With Tax',
type: 'number',
required: true,
readOnly: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 175,
value: (objectData) => {
if (objectData?.taxRate?.rateType == 'percentage') {
return (
(
(objectData?.totalCost || 0) *
(1 + objectData?.taxRate?.rate / 100)
).toFixed(2) || undefined
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
(
(objectData?.totalCost || 0) + objectData?.taxRate?.rate
).toFixed(2) || undefined
)
} else {
return objectData?.totalCost || undefined
}
}
}
],
rollups: [
{
name: 'totalQuantity',
label: 'Total',
type: 'number',
property: 'quantity',
value: (objectData) => {
return objectData?.items?.reduce(
(acc, item) => acc + item.quantity,
0
)
}
},
{
name: 'totalCost',
label: 'Total',
type: 'number',
prefix: '£',
property: 'totalCost',
value: (objectData) => {
return objectData?.items
?.reduce((acc, item) => acc + (item.totalCost || 0), 0)
.toFixed(2)
}
},
{
name: 'totalCostWithTax',
label: 'Total',
type: 'number',
prefix: '£',
property: 'totalCostWithTax',
value: (objectData) => {
return objectData?.items
?.reduce((acc, item) => acc + (item.totalCostWithTax || 0), 0)
.toFixed(2)
}
}
]
objectType: 'vendor',
showHyperlink: true
},
{
name: 'cost',

View File

@ -0,0 +1,280 @@
import ShipmentIcon from '../../components/Icons/ShipmentIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Shipment = {
name: 'shipment',
label: 'Shipment',
prefix: 'SHM',
icon: ShipmentIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) => `/dashboard/inventory/shipments/info?shipmentId=${_id}`
}
],
group: ['vendor', 'purchaseOrder'],
filters: ['vendor', 'purchaseOrder', 'state', 'courierService'],
sorters: [
'createdAt',
'state',
'updatedAt',
'shippedDate',
'expectedDeliveryDate'
],
columns: [
'_id',
'createdAt',
'state',
'updatedAt',
'vendor',
'purchaseOrder',
'trackingNumber'
],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
columnFixed: 'left',
objectType: 'shipment',
columnWidth: 140,
showCopy: true
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true
},
{ name: 'state', label: 'State', type: 'state', readOnly: true },
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true
},
{
name: 'purchaseOrder',
label: 'Purchase Order',
required: true,
type: 'object',
objectType: 'purchaseOrder',
showHyperlink: true
},
{
name: 'vendor',
label: 'Vendor',
required: true,
type: 'object',
objectType: 'vendor',
showHyperlink: true
},
{
name: 'courierService',
label: 'Courier Service',
required: false,
type: 'object',
objectType: 'courierService'
},
{
name: 'trackingNumber',
label: 'Tracking Number',
type: 'string',
required: false
},
{
name: 'items',
label: 'Shipment Items',
type: 'objectChildren',
required: true,
properties: [
{
name: 'itemType',
label: 'Item Type',
type: 'objectType',
masterFilter: ['part', 'packaging'],
required: true
},
{
name: 'item',
label: 'Item',
type: 'object',
objectType: (objectData) => {
return objectData?.itemType
},
required: true,
showHyperlink: true
},
{
name: 'itemCost',
label: 'Item Cost',
type: 'number',
required: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 150,
value: (objectData) => {
if (objectData?.item) {
return objectData?.item?.cost || undefined
} else {
return undefined
}
}
},
{
name: 'quantity',
label: 'Quantity',
type: 'number',
required: true,
columnWidth: 150
},
{
name: 'totalCost',
label: 'Total Cost',
type: 'number',
required: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 150,
value: (objectData) => {
return (
(objectData?.itemCost || 0) * (objectData?.quantity || 0) ||
undefined
)
}
},
{
name: 'taxRate',
label: 'Tax Rate',
type: 'object',
objectType: 'taxRate',
showHyperlink: true,
value: (objectData) => {
if (objectData?.item) {
return objectData?.item?.costTaxRate || undefined
} else {
return undefined
}
}
},
{
name: 'totalCostWithTax',
label: 'Total Cost w/ Tax',
type: 'number',
required: true,
readOnly: true,
prefix: '£',
min: 0,
step: 0.01,
columnWidth: 175,
value: (objectData) => {
if (objectData?.taxRate?.rateType == 'percentage') {
return (
(
(objectData?.totalCost || 0) *
(1 + objectData?.taxRate?.rate / 100)
).toFixed(2) || undefined
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
(
(objectData?.totalCost || 0) + objectData?.taxRate?.rate
).toFixed(2) || undefined
)
} else {
return objectData?.totalCost || undefined
}
}
}
],
rollups: [
{
name: 'totalQuantity',
label: 'Total',
type: 'number',
property: 'quantity',
value: (objectData) => {
return objectData?.items?.reduce(
(acc, item) => acc + item.quantity,
0
)
}
},
{
name: 'totalCost',
label: 'Total',
type: 'number',
prefix: '£',
property: 'totalCost',
value: (objectData) => {
return objectData?.items
?.reduce((acc, item) => acc + (item.totalCost || 0), 0)
.toFixed(2)
}
},
{
name: 'totalCostWithTax',
label: 'Total',
type: 'number',
prefix: '£',
property: 'totalCostWithTax',
value: (objectData) => {
return objectData?.items
?.reduce((acc, item) => acc + (item.totalCostWithTax || 0), 0)
.toFixed(2)
}
}
]
},
{
name: 'cost',
label: 'Cost',
type: 'netGross',
required: true,
prefix: '£',
min: 0,
step: 0.01,
value: (objectData) => {
const net = objectData?.items?.reduce(
(acc, item) => acc + (item.totalCost || 0),
0
)
const gross = objectData?.items?.reduce(
(acc, item) => acc + (item.totalCostWithTax || 0),
0
)
return { net: net, gross: gross }
}
},
{
name: 'shippedDate',
label: 'Shipped Date',
type: 'dateTime',
required: false
},
{
name: 'expectedDeliveryDate',
label: 'Expected Delivery Date',
type: 'dateTime',
required: false
},
{
name: 'actualDeliveryDate',
label: 'Actual Delivery Date',
type: 'dateTime',
required: false
},
{
name: 'notes',
label: 'Notes',
type: 'textarea',
required: false
}
]
}

View File

@ -6,8 +6,8 @@ export const StockEvent = {
prefix: 'SEV',
icon: StockEventIcon,
actions: [],
columns: ['_id', 'owner', 'owner._id', 'parent._id', 'value', 'createdAt'],
filters: ['_id', 'owner._id', 'parent._id'],
columns: ['_id', 'owner', 'parent', 'value', 'createdAt'],
filters: ['_id', 'owner', 'parent'],
sorters: ['createdAt'],
properties: [
{
@ -42,18 +42,8 @@ export const StockEvent = {
},
columnFixed: 'left',
value: null,
showCopy: true
},
{
name: 'owner._id',
label: 'Owner ID',
type: 'id',
objectType: (objectData) => {
return objectData.ownerType
},
columnFixed: 'left',
showHyperlink: true,
showCopy: true
showCopy: true,
showHyperlink: true
},
{
name: 'parent',
@ -63,17 +53,8 @@ export const StockEvent = {
return objectData?.parentType
},
value: null,
showCopy: true
},
{
name: 'parent._id',
label: 'Parent ID',
type: 'id',
objectType: (objectData) => {
return objectData.parentType
},
showHyperlink: true,
showCopy: true
showCopy: true,
showHyperlink: true
},
{
name: 'value',

View File

@ -28,8 +28,8 @@ export const SubJob = {
}
}
],
columns: ['_id', 'printer', 'printer._id', 'job._id', 'state', 'createdAt'],
filters: ['state', '_id', 'job._id', 'printer._id'],
columns: ['_id', 'printer', 'job', 'state', 'createdAt'],
filters: ['state', '_id', 'job', 'printer'],
sorters: ['createdAt', 'state'],
group: ['job'],
properties: [
@ -102,15 +102,8 @@ export const SubJob = {
name: 'job',
label: 'Job',
type: 'object',
objectType: 'job'
},
{
name: 'job._id',
label: 'Job ID',
type: 'id',
columnWidth: 140,
showHyperlink: true,
objectType: 'job'
objectType: 'job',
showHyperlink: true
},
{
@ -118,16 +111,8 @@ export const SubJob = {
label: 'Printer',
type: 'object',
columnFixed: 'left',
objectType: 'printer'
},
{
name: 'printer._id',
label: 'Printer ID',
type: 'id',
columnWidth: 140,
columnFixed: 'left',
showHyperlink: true,
objectType: 'printer'
objectType: 'printer',
showHyperlink: true
}
]
}

View File

@ -101,15 +101,8 @@ export const TaxRecord = {
label: 'Tax Rate',
required: true,
type: 'object',
objectType: 'taxRate'
},
{
name: 'taxRate._id',
label: 'Tax Rate ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: 'taxRate'
objectType: 'taxRate',
showHyperlink: true
},
{
name: 'transactionType',
@ -129,17 +122,8 @@ export const TaxRecord = {
type: 'object',
objectType: (objectData) => {
return objectData?.transactionType || 'purchaseOrder'
}
},
{
name: 'transaction._id',
label: 'Transaction ID',
readOnly: true,
type: 'id',
showHyperlink: true,
objectType: (objectData) => {
return objectData?.transactionType || 'purchaseOrder'
}
},
showHyperlink: true
},
{
name: 'amount',

View File

@ -69,9 +69,34 @@ export const Vendor = {
`/dashboard/management/vendors/info?vendorId=${_id}&action=delete`
}
],
columns: ['name', '_id', 'country', 'email', 'website', 'createdAt'],
filters: ['name', '_id', 'country', 'email'],
sorters: ['name', 'country', 'email', 'createdAt', '_id'],
columns: [
'name',
'_id',
'country',
'email',
'website',
'active',
'createdAt',
'updatedAt'
],
filters: [
'name',
'_id',
'country',
'email',
'active',
'createdAt',
'updatedAt'
],
sorters: [
'name',
'country',
'email',
'active',
'createdAt',
'updatedAt',
'_id'
],
group: ['country'],
properties: [
{
@ -108,6 +133,13 @@ export const Vendor = {
readOnly: false,
required: false
},
{
name: 'active',
label: 'Active',
type: 'bool',
readOnly: false,
required: true
},
{
name: 'country',
label: 'Country',
@ -137,6 +169,55 @@ export const Vendor = {
type: 'url',
readOnly: false,
required: false
},
{
name: 'address.building',
label: 'Building',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.addressLine1',
label: 'Address Line 1',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.addressLine2',
label: 'Address Line 2',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.city',
label: 'City',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.state',
label: 'State',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.postcode',
label: 'Postcode',
type: 'text',
readOnly: false,
required: false
},
{
name: 'address.country',
label: 'Country',
type: 'country',
readOnly: false,
required: false
}
]
}

View File

@ -9,6 +9,10 @@ import StockAudits from '../components/Dashboard/Inventory/StockAudits.jsx'
import StockAuditInfo from '../components/Dashboard/Inventory/StockAudits/StockAuditInfo.jsx'
import PurchaseOrders from '../components/Dashboard/Inventory/PurchaseOrders.jsx'
import PurchaseOrderInfo from '../components/Dashboard/Inventory/PurchaseOrders/PurchaseOrderInfo.jsx'
import OrderItems from '../components/Dashboard/Inventory/OrderItems.jsx'
import OrderItemInfo from '../components/Dashboard/Inventory/OrderItems/OrderItemInfo.jsx'
import Shipments from '../components/Dashboard/Inventory/Shipments.jsx'
import ShipmentInfo from '../components/Dashboard/Inventory/Shipments/ShipmentInfo.jsx'
const InventoryRoutes = [
<Route
@ -55,6 +59,22 @@ const InventoryRoutes = [
key='purchaseorders-info'
path='inventory/purchaseorders/info'
element={<PurchaseOrderInfo />}
/>,
<Route
key='orderitems'
path='inventory/orderitems'
element={<OrderItems />}
/>,
<Route
key='orderitems-info'
path='inventory/orderitems/info'
element={<OrderItemInfo />}
/>,
<Route key='shipments' path='inventory/shipments' element={<Shipments />} />,
<Route
key='shipments-info'
path='inventory/shipments/info'
element={<ShipmentInfo />}
/>
]