Compare commits

...

3 Commits

Author SHA1 Message Date
127fefc39a Update Jenkinsfile to allow script execution during dependency installation
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
2026-06-14 23:55:08 +01:00
fb9454d8e0 Refactor Stock Transfers and Management Components
- Adjusted width of Stock Transfers modal for better UI consistency.
- Enhanced New Stock Transfer form with default transfer name generation and improved validation handling.
- Added Product Categories to Management Sidebar and updated routing for better navigation.
- Implemented delete functionality in Host Info component.
- Improved User Info layout for mobile responsiveness and added ObjectProperty component for better data display.
- Updated FileUpload and FileList components to support minimal display mode.
- Enhanced ObjectTable with subscription filtering and improved rendering logic.
- Added new properties and filters to Stock Transfer and Product models for better data management.
2026-06-14 23:51:45 +01:00
7a5ea5416b Numerious fixes. 2026-05-17 19:11:25 +01:00
104 changed files with 1321 additions and 659 deletions

2
Jenkinsfile vendored
View File

@ -14,7 +14,7 @@ def deploy() {
stage('Install Dependencies (Ubuntu)') {
nodejs(nodeJSInstallationName: 'Node23') {
sh 'pnpm install --frozen-lockfile --production=false'
sh 'pnpm install --frozen-lockfile --production=false --ignore-scripts=false'
}
}

View File

@ -0,0 +1,16 @@
<?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.983114,0,0,0.983114,0.872652,-3.62694)">
<path d="M29.119,60.411L19.595,65.918C18.255,66.699 16.701,66.699 15.368,65.918L3.47,59.042C2.153,58.292 1.364,56.929 1.364,55.39L1.364,41.653C1.364,40.114 2.153,38.751 3.47,37.993L15.368,31.125C15.408,31.101 15.448,31.079 15.488,31.057C15.484,30.981 15.482,30.906 15.482,30.83L15.482,17.094C15.482,15.554 16.271,14.192 17.588,13.433L29.487,6.565C30.819,5.777 32.374,5.777 33.714,6.565L45.605,13.433C46.922,14.192 47.71,15.554 47.71,17.094L47.71,30.83C47.71,30.882 47.71,30.934 47.708,30.986C47.794,31.029 47.879,31.075 47.963,31.125L59.854,37.993C60.373,38.292 60.811,38.685 61.15,39.146C60.959,39.134 60.762,39.128 60.56,39.128L54.077,39.128L46.646,34.839C46.14,34.532 45.551,34.532 45.045,34.839L37.619,39.128L35.314,39.128C33.245,39.128 31.768,39.759 30.759,40.772C29.905,41.629 29.317,42.828 29.16,44.457L19.411,50.038L19.411,61.546C19.603,61.446 19.687,61.393 19.886,61.278L28.929,56.048C29.434,55.75 30.678,54.945 30.755,55.126L30.857,55.226L30.757,55.324C29.753,56.334 29.119,57.815 29.119,59.896L29.119,60.411ZM33.53,36.986C33.721,36.887 33.805,36.833 34.005,36.718L43.047,31.489C43.553,31.19 43.844,30.685 43.844,30.103L43.844,19.574L33.53,25.478L33.53,36.986ZM29.663,36.94L29.663,25.531L19.341,19.62L19.341,30.103C19.341,30.57 19.539,30.987 19.878,31.288L29.663,36.94ZM31.554,22.132L42.251,15.999C42.197,15.953 42.182,15.93 42.067,15.861L32.397,10.279C31.891,9.973 31.302,9.973 30.796,10.279L21.133,15.861C21.011,15.93 20.98,15.96 20.896,16.029L31.554,22.132ZM17.928,34.679C17.513,34.548 17.07,34.601 16.678,34.839L7.015,40.42C6.892,40.489 6.862,40.52 6.777,40.589L17.436,46.692L28.117,40.567L17.928,34.679ZM15.545,61.546L15.545,50.091L5.223,44.18L5.223,54.662C5.223,55.244 5.529,55.75 6.035,56.048L15.261,61.385C15.391,61.462 15.414,61.477 15.545,61.546Z"/>
</g>
<g transform="matrix(0.446187,0,0,0.446187,32,37.312437)">
<path d="M46.094,59.812L63.672,59.812C69.125,59.812 71.719,57.234 71.719,51.688L71.719,40.219C71.719,34.703 69.125,32.109 63.672,32.109L46.094,32.109C40.641,32.109 38.047,34.703 38.047,40.219L38.047,51.688C38.047,57.234 40.641,59.812 46.094,59.812ZM46.109,53.375C44.984,53.375 44.484,52.844 44.484,51.734L44.484,40.203C44.484,39.078 44.984,38.547 46.109,38.547L63.672,38.547C64.766,38.547 65.281,39.078 65.281,40.203L65.281,51.734C65.281,52.844 64.766,53.375 63.672,53.375L46.109,53.375Z" style="fill-rule:nonzero;"/>
<path d="M8.047,59.812L25.625,59.812C31.078,59.812 33.672,57.234 33.672,51.688L33.672,40.219C33.672,34.703 31.078,32.109 25.625,32.109L8.047,32.109C2.594,32.109 0,34.703 0,40.219L0,51.688C0,57.234 2.594,59.812 8.047,59.812ZM8.047,53.375C6.938,53.375 6.438,52.844 6.438,51.734L6.438,40.203C6.438,39.078 6.938,38.547 8.047,38.547L25.609,38.547C26.703,38.547 27.234,39.078 27.234,40.203L27.234,51.734C27.234,52.844 26.703,53.375 25.609,53.375L8.047,53.375Z" style="fill-rule:nonzero;"/>
<path d="M46.094,27.75L63.672,27.75C69.125,27.75 71.719,25.156 71.719,19.625L71.719,8.172C71.719,2.625 69.125,0.062 63.672,0.062L46.094,0.062C40.641,0.062 38.047,2.625 38.047,8.172L38.047,19.625C38.047,25.156 40.641,27.75 46.094,27.75ZM46.109,21.312C44.984,21.312 44.484,20.797 44.484,19.656L44.484,8.125C44.484,7.016 44.984,6.5 46.109,6.5L63.672,6.5C64.766,6.5 65.281,7.016 65.281,8.125L65.281,19.656C65.281,20.797 64.766,21.312 63.672,21.312L46.109,21.312Z" style="fill-rule:nonzero;"/>
<path d="M8.047,27.75L25.625,27.75C31.078,27.75 33.672,25.156 33.672,19.625L33.672,8.172C33.672,2.625 31.078,0.062 25.625,0.062L8.047,0.062C2.594,0.062 0,2.625 0,8.172L0,19.625C0,25.156 2.594,27.75 8.047,27.75ZM8.047,21.312C6.938,21.312 6.438,20.797 6.438,19.656L6.438,8.125C6.438,7.016 6.938,6.5 8.047,6.5L25.609,6.5C26.703,6.5 27.234,7.016 27.234,8.125L27.234,19.656C27.234,20.797 26.703,21.312 25.609,21.312L8.047,21.312Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.446187,0,0,0.446187,32,37.312437)">
<path d="M46.094,59.812L63.672,59.812C69.125,59.812 71.719,57.234 71.719,51.688L71.719,40.219C71.719,34.703 69.125,32.109 63.672,32.109L46.094,32.109C40.641,32.109 38.047,34.703 38.047,40.219L38.047,51.688C38.047,57.234 40.641,59.812 46.094,59.812ZM46.109,53.375C44.984,53.375 44.484,52.844 44.484,51.734L44.484,40.203C44.484,39.078 44.984,38.547 46.109,38.547L63.672,38.547C64.766,38.547 65.281,39.078 65.281,40.203L65.281,51.734C65.281,52.844 64.766,53.375 63.672,53.375L46.109,53.375Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -64,10 +64,6 @@ const InvoiceInfo = () => {
const [newPaymentOpen, setNewPaymentOpen] = useState(false)
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
@ -313,7 +309,7 @@ const InvoiceInfo = () => {
<PostInvoice
onOk={() => {
setPostInvoiceOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -331,7 +327,7 @@ const InvoiceInfo = () => {
<AcknowledgeInvoice
onOk={() => {
setAcknowledgeInvoiceOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -349,7 +345,7 @@ const InvoiceInfo = () => {
<NewPayment
onOk={() => {
setNewPaymentOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
reset={newPaymentOpen}
defaultValues={{

View File

@ -50,10 +50,6 @@ const PaymentInfo = () => {
const [postPaymentOpen, setPostPaymentOpen] = useState(false)
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
@ -233,7 +229,7 @@ const PaymentInfo = () => {
<PostPayment
onOk={() => {
setPostPaymentOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>

View File

@ -47,10 +47,6 @@ const FilamentStockInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -49,10 +49,6 @@ const OrderItemInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -51,10 +51,6 @@ const PartStockInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -58,10 +58,6 @@ const ProductStockInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false
@ -262,7 +258,7 @@ const ProductStockInfo = () => {
<PostProductStock
onOk={() => {
setPostProductStockOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>

View File

@ -74,10 +74,6 @@ const PurchaseOrderInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
orderItemsTableRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
@ -427,7 +423,7 @@ const PurchaseOrderInfo = () => {
<PostPurchaseOrder
onOk={() => {
setPostPurchaseOrderOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -445,7 +441,7 @@ const PurchaseOrderInfo = () => {
<AcknowledgePurchaseOrder
onOk={() => {
setAcknowledgePurchaseOrderOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -463,7 +459,7 @@ const PurchaseOrderInfo = () => {
<CancelPurchaseOrder
onOk={() => {
setCancelPurchaseOrderOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>

View File

@ -64,10 +64,6 @@ const PurchaseOrderInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
orderItemsTableRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
@ -332,7 +328,7 @@ const PurchaseOrderInfo = () => {
<PostPurchaseOrder
onOk={() => {
setPostPurchaseOrderOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -350,7 +346,7 @@ const PurchaseOrderInfo = () => {
<AcknowledgePurchaseOrder
onOk={() => {
setAcknowledgePurchaseOrderOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>

View File

@ -57,10 +57,6 @@ const ShipmentInfo = () => {
const [receiveShipmentOpen, setReceiveShipmentOpen] = useState(false)
const [cancelShipmentOpen, setCancelShipmentOpen] = useState(false)
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
@ -261,7 +257,7 @@ const ShipmentInfo = () => {
<ShipShipment
onOk={() => {
setShipShipmentOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -279,7 +275,7 @@ const ShipmentInfo = () => {
<ReceiveShipment
onOk={() => {
setReceiveShipmentOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -297,7 +293,7 @@ const ShipmentInfo = () => {
<CancelShipment
onOk={() => {
setCancelShipmentOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>

View File

@ -51,10 +51,6 @@ const StockAuditInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -52,10 +52,6 @@ const StockLocationInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -90,7 +90,7 @@ const StockTransfers = () => {
open={newOpen}
styles={{ content: { paddingBottom: '24px' } }}
footer={null}
width={960}
width={740}
onCancel={() => {
setNewOpen(false)
}}

View File

@ -1,17 +1,40 @@
import PropTypes from 'prop-types'
import dayjs from 'dayjs'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const defaultTransferName = () =>
`Transfer ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`
const NewStockTransfer = ({ onOk, reset }) => {
return (
<NewObjectForm
type={'stockTransfer'}
reset={reset}
defaultValues={{ state: { type: 'draft' }, lines: [] }}
defaultValues={{
name: defaultTransferName(),
state: { type: 'draft' },
lines: []
}}
>
{({ handleSubmit, submitLoading, objectData }) => {
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='stockTransfer'
column={1}
bordered={false}
labelWidth={80}
isEditing={true}
required={true}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
@ -20,6 +43,7 @@ const NewStockTransfer = ({ onOk, reset }) => {
type='stockTransfer'
column={1}
bordered={false}
labelWidth={80}
visibleProperties={{
_id: false,
createdAt: false,
@ -38,7 +62,7 @@ const NewStockTransfer = ({ onOk, reset }) => {
<WizardView
steps={steps}
loading={submitLoading}
formValid={true}
formValid={formValid}
title='New Stock Transfer'
onSubmit={async () => {
const result = await handleSubmit()

View File

@ -53,10 +53,6 @@ const StockTransferInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false
@ -257,7 +253,7 @@ const StockTransferInfo = () => {
<PostStockTransfer
onOk={() => {
setPostOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>

View File

@ -48,10 +48,6 @@ const AppPasswordInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
regenerateSecret: () => {
setRegenerateSecretOpen(true)
return false

View File

@ -50,10 +50,6 @@ const CourierServiceInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -46,10 +46,6 @@ const CourierInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -51,10 +51,6 @@ const DocumentJobInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -53,10 +53,6 @@ const DocumentPrinterInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -51,10 +51,6 @@ const DocumentSizeInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -47,10 +47,6 @@ const DocumentTemplateDesign = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -53,10 +53,6 @@ const DocumentTemplateInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -43,10 +43,6 @@ const FilamentSkuInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -57,10 +57,6 @@ const FilamentInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
newFilamentSku: () => {
setNewFilamentSkuOpen(true)
return true
@ -218,10 +214,9 @@ const FilamentInfo = () => {
) : (
<ObjectTable
type='filamentStock'
masterFilter={{ 'filamentSku.filament._id': filamentId }}
masterFilter={{ filament: filamentId }}
visibleColumns={{
filamentSku: false,
'filamentSku.filament._id': false,
filament: false,
startingWeight: false
}}
/>

View File

@ -52,10 +52,6 @@ const FileInfo = () => {
const { fetchFileContent } = useContext(ApiServerContext)
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -53,10 +53,6 @@ const HostInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
hostOTP: () => {
setHostOTPOpen(true)
return false
@ -72,6 +68,10 @@ const HostInfo = () => {
finishEdit: () => {
objectFormRef?.current.handleUpdate()
return true
},
delete: () => {
objectFormRef?.current?.handleDelete?.()
return true
}
}

View File

@ -5,6 +5,7 @@ import FilamentSkuIcon from '../../Icons/FilamentSkuIcon'
import PartIcon from '../../Icons/PartIcon'
import PartSkuIcon from '../../Icons/PartSkuIcon'
import ProductIcon from '../../Icons/ProductIcon'
import ProductCategoryIcon from '../../Icons/ProductCategoryIcon'
import ProductSkuIcon from '../../Icons/ProductSkuIcon'
import VendorIcon from '../../Icons/VendorIcon'
import MaterialIcon from '../../Icons/MaterialIcon'
@ -57,6 +58,12 @@ const items = [
label: 'Products',
path: '/dashboard/management/products'
},
{
key: 'productCategories',
icon: <ProductCategoryIcon />,
label: 'Product Categories',
path: '/dashboard/management/productcategories'
},
{
key: 'productSkus',
icon: <ProductSkuIcon />,
@ -199,6 +206,7 @@ const routeKeyMap = {
'/dashboard/management/users': 'users',
'/dashboard/management/apppasswords': 'appPasswords',
'/dashboard/management/products': 'products',
'/dashboard/management/productcategories': 'productCategories',
'/dashboard/management/productskus': 'productSkus',
'/dashboard/management/vendors': 'vendors',
'/dashboard/management/couriers': 'couriers',

View File

@ -45,10 +45,6 @@ const MaterialInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -41,10 +41,6 @@ const NoteTypeInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -50,10 +50,6 @@ const NoteInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -40,10 +40,6 @@ const PartSkuInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -45,10 +45,6 @@ const PartInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
newPartSku: () => {
setNewPartSkuOpen(true)
return true

View File

@ -0,0 +1,109 @@
import { useRef, useState } from 'react'
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
import NewProductCategory from './ProductCategories/NewProductCategory'
import useColumnVisibility from '../hooks/useColumnVisibility'
import ColumnViewButton from '../common/ColumnViewButton'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import useViewMode from '../hooks/useViewMode'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import ExportListButton from '../common/ExportListButton'
const ProductCategories = () => {
const [newProductCategoryOpen, setNewProductCategoryOpen] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('productCategory')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('productCategory')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('ProductCategories')
const actionItems = {
items: [
{
label: 'New Product Category',
key: 'newProductCategory',
icon: <PlusIcon />
},
{ type: 'divider' },
{
label: 'Reload List',
key: 'reloadList',
icon: <ReloadIcon />
}
],
onClick: ({ key }) => {
if (key === 'reloadList') {
tableRef.current?.reload()
} else if (key === 'newProductCategory') {
setNewProductCategoryOpen(true)
}
}
}
return (
<>
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify={'space-between'}>
<Space>
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ColumnViewButton
type='productCategory'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='productCategory' />
</Space>
<Space>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton
viewMode={viewMode}
setViewMode={setViewMode}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
type='productCategory'
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
/>
<Modal
open={newProductCategoryOpen}
footer={null}
width={700}
onCancel={() => {
setNewProductCategoryOpen(false)
}}
>
<NewProductCategory
onOk={() => {
setNewProductCategoryOpen(false)
tableRef.current?.reload()
}}
reset={newProductCategoryOpen}
/>
</Modal>
</Flex>
</>
)
}
export default ProductCategories

View File

@ -0,0 +1,68 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewProductCategory = ({ onOk }) => {
return (
<NewObjectForm type={'productCategory'}>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='productCategory'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='productCategory'
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 Product Category'
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk()
}
}}
/>
)
}}
</NewObjectForm>
)
}
NewProductCategory.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
}
export default NewProductCategory

View File

@ -0,0 +1,195 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
import InfoCollapse from '../../common/InfoCollapse'
import ObjectInfo from '../../common/ObjectInfo'
import ViewButton from '../../common/ViewButton'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ObjectForm from '../../common/ObjectForm'
import EditButtons from '../../common/EditButtons'
import LockIndicator from '../../common/LockIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.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 UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
const ProductCategoryInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const productCategoryId = new URLSearchParams(location.search).get(
'productCategoryId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'ProductCategoryInfo',
{
info: true,
notes: true,
auditLogs: true
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
lock: null,
loading: false,
objectData: {}
})
const actions = {
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='productCategory'
id={productCategoryId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Product Category Information' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='productCategory'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<DocumentPrintButton
type='productCategory'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
<Space>
<EditButtons
isEditing={objectFormState.isEditing}
handleUpdate={() => {
actionHandlerRef.current.callAction('finishEdit')
}}
cancelEditing={() => {
actionHandlerRef.current.callAction('cancelEdit')
}}
startEditing={() => {
actionHandlerRef.current.callAction('edit')
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Product Category Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
>
<ObjectForm
id={productCategoryId}
type='productCategory'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<ObjectInfo
loading={loading}
isEditing={isEditing}
type='productCategory'
objectData={objectData}
/>
)}
</ObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={productCategoryId} type='productCategory' />
</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': productCategoryId }}
visibleColumns={{ _id: false, 'parent._id': false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
</>
)
}
export default ProductCategoryInfo

View File

@ -47,10 +47,6 @@ const ProductSkuInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -46,10 +46,6 @@ const ProductInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.fetchObject?.()
return true
},
newProductSku: () => {
setNewProductSkuOpen(true)
return true

View File

@ -45,10 +45,6 @@ const TaxRateInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -48,10 +48,6 @@ const TaxRecordInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -22,6 +22,9 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import NewAppPassword from '../AppPasswords/NewAppPassword.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import { getModelProperty } from '../../../../database/ObjectModels.js'
import { useMediaQuery } from 'react-responsive'
const UserInfo = () => {
const location = useLocation()
@ -44,12 +47,9 @@ const UserInfo = () => {
loading: false,
objectData: {}
})
const isMobile = useMediaQuery({ maxWidth: 768 })
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
newAppPassword: () => {
setNewAppPasswordOpen(true)
return false
@ -151,13 +151,34 @@ const UserInfo = () => {
}}
>
{({ loading, isEditing, objectData }) => (
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='user'
objectData={objectData}
/>
<Flex gap='large' vertical={isMobile}>
<div
style={
isMobile
? { width: '100%' }
: { width: '20%', maxWidth: '238px' }
}
>
<Card styles={{ body: { padding: 18 } }}>
<ObjectProperty
{...getModelProperty('user', 'profileImage')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
/>
</Card>
</div>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='user'
objectData={objectData}
visibleProperties={{
profileImage: false
}}
/>
</Flex>
)}
</ObjectForm>
</InfoCollapse>

View File

@ -45,10 +45,6 @@ const VendorInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -20,11 +20,8 @@ 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 EyeIcon from '../../../Icons/EyeIcon.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
import FilePreview from '../../common/FilePreview.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelProperty } from '../../../../database/ObjectModels.js'
import PartIcon from '../../../Icons/PartIcon.jsx'
@ -42,7 +39,6 @@ const GCodeFileInfo = () => {
{
info: true,
parts: true,
preview: true,
notes: true,
auditLogs: true
}
@ -58,10 +54,6 @@ const GCodeFileInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false
@ -100,7 +92,6 @@ const GCodeFileInfo = () => {
items={[
{ key: 'info', label: 'GCode File Information' },
{ key: 'parts', label: 'Parts' },
{ key: 'preview', label: 'GCode File Preview' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
@ -196,26 +187,6 @@ const GCodeFileInfo = () => {
loading={loading}
/>
</InfoCollapse>
<InfoCollapse
title='GCode File Preview'
icon={<EyeIcon />}
active={collapseState.preview}
onToggle={(expanded) =>
updateCollapseState('preview', expanded)
}
collapseKey='preview'
>
{objectData?.file?._id ? (
<Card>
<FilePreview
file={objectData?.file}
style={{ width: '100%', height: '100%' }}
/>
</Card>
) : (
<MissingPlaceholder message={'No file.'} />
)}
</InfoCollapse>
</Flex>
)
}}

View File

@ -0,0 +1,140 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import loglevel from 'loglevel'
import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.jsx'
import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx'
import ViewButton from '../../common/ViewButton.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import EyeIcon from '../../../Icons/EyeIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx'
import LockIndicator from '../../common/LockIndicator.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import FilePreview from '../../common/FilePreview.jsx'
import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
const log = loglevel.getLogger('GCodeFilePreview')
log.setLevel(config.logLevel)
const GCodeFilePreview = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId')
const [collapseState, updateCollapseState] = useCollapseState(
'GCodeFilePreview',
{
preview: true,
notes: true
}
)
const [objectFormState, setEditFormState] = useState({
loading: false,
objectData: {},
lock: null
})
return (
<Flex
gap='large'
vertical='true'
style={{
maxHeight: '100%',
minHeight: 0
}}
>
<Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'>
<ObjectActions
type='gcodeFile'
id={gcodeFileId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
visibleActions={{ edit: false }}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'preview', label: 'GCode File Preview' },
{ key: 'notes', label: 'Notes' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='gcodeFile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<DocumentPrintButton
type='gcodeFile'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<LockIndicator lock={objectFormState.lock} />
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ObjectForm
id={gcodeFileId}
type='gcodeFile'
style={{ height: '100%' }}
ref={objectFormRef}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, objectData }) => (
<InfoCollapse
title='GCode File Preview'
icon={<EyeIcon />}
active={collapseState.preview}
onToggle={(expanded) =>
updateCollapseState('preview', expanded)
}
collapseKey='preview'
>
{loading ? (
<InfoCollapsePlaceholder />
) : objectData?.file?._id ? (
<Card>
<FilePreview
file={objectData?.file}
style={{ width: '100%', height: '72vh' }}
/>
</Card>
) : (
<MissingPlaceholder message={'No file.'} />
)}
</InfoCollapse>
)}
</ObjectForm>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={gcodeFileId} type='gcodeFile' />
</Card>
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
)
}
export default GCodeFilePreview

View File

@ -18,7 +18,7 @@ const NewGCodeFile = ({ onOk, defaultValues }) => {
const steps = [
{
title: 'Upload',
key: 'uplaod',
key: 'upload',
content: (
<ObjectInfo
type='gcodeFile'
@ -27,7 +27,7 @@ const NewGCodeFile = ({ onOk, defaultValues }) => {
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{ name: false, filament: false }}
visibleProperties={{ file: true }}
showLabels={false}
/>
)
@ -57,6 +57,8 @@ const NewGCodeFile = ({ onOk, defaultValues }) => {
bordered={false}
visibleProperties={{
_id: false,
_reference: false,
parts: false,
createdAt: false,
updatedAt: false,
startedAt: false

View File

@ -54,10 +54,6 @@ const JobInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -85,10 +85,6 @@ const ControlPrinter = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -48,10 +48,6 @@ const PrinterInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -42,10 +42,6 @@ const SubJobInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current.handleFetchObject()
return true
},
edit: () => {
objectFormRef?.current.startEditing()
return false

View File

@ -45,10 +45,6 @@ const ClientInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false

View File

@ -55,10 +55,6 @@ const ListingVarientInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
@ -226,7 +222,7 @@ const ListingVarientInfo = () => {
objectData={objectFormState.objectData}
onOk={() => {
setPublishOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
/>
</Modal>
@ -242,7 +238,7 @@ const ListingVarientInfo = () => {
objectData={objectFormState.objectData}
onOk={() => {
setUnpublishOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
/>
</Modal>

View File

@ -55,10 +55,6 @@ const ListingInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
@ -269,7 +265,7 @@ const ListingInfo = () => {
objectData={objectFormState.objectData}
onOk={() => {
setPublishListingOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
listingVarientsTableRef.current?.reload?.()
}}
/>
@ -286,7 +282,7 @@ const ListingInfo = () => {
objectData={objectFormState.objectData}
onOk={() => {
setUnpublishListingOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
listingVarientsTableRef.current?.reload?.()
}}
/>

View File

@ -112,10 +112,6 @@ const MarketplaceInfo = () => {
}
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
@ -295,7 +291,7 @@ const MarketplaceInfo = () => {
<SyncListings
onOk={() => {
setSyncListingsOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -311,7 +307,7 @@ const MarketplaceInfo = () => {
<SyncOrders
onOk={() => {
setSyncOrdersOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>

View File

@ -71,10 +71,6 @@ const SalesOrderInfo = () => {
})
const actions = {
reload: () => {
objectFormRef?.current?.handleFetchObject?.()
return true
},
edit: () => {
orderItemsTableRef?.current?.startEditing?.()
objectFormRef?.current?.startEditing?.()
@ -424,7 +420,7 @@ const SalesOrderInfo = () => {
<PostSalesOrder
onOk={() => {
setPostSalesOrderOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -442,7 +438,7 @@ const SalesOrderInfo = () => {
<ConfirmSalesOrder
onOk={() => {
setConfirmSalesOrderOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
@ -460,7 +456,7 @@ const SalesOrderInfo = () => {
<CancelSalesOrder
onOk={() => {
setCancelSalesOrderOpen(false)
actions.reload()
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>

View File

@ -12,10 +12,12 @@ const breadcrumbNameMap = {
developer: 'Developer',
finance: 'Finance',
sales: 'Sales',
productcategories: 'Product Categories',
overview: 'Overview',
info: 'Info',
design: 'Design',
control: 'Control'
control: 'Control',
preview: 'Preview'
}
const mainSections = ['production', 'inventory', 'management', 'developer']

View File

@ -23,6 +23,7 @@ const FileList = ({
showInfo = true,
showDownload = true,
defaultPreviewOpen = false,
minimal = false,
card = true
}) => {
const { fetchFileContent, flushFile } = useContext(ApiServerContext)
@ -78,7 +79,7 @@ const FileList = ({
<Tag>{file.extension}</Tag>
</Flex>
<Flex gap={'small'} align='center'>
{showDownload && (
{showDownload && !minimal && (
<Button
icon={<DownloadIcon />}
size='small'
@ -86,7 +87,7 @@ const FileList = ({
onClick={() => handleDownload(file)}
/>
)}
{showPreview && (
{showPreview && !minimal && (
<Button
icon={previewOpen ? <EyeSlashIcon /> : <EyeIcon />}
size='small'
@ -100,7 +101,7 @@ const FileList = ({
}}
/>
)}
{showInfo && (
{showInfo && !minimal && (
<Button
icon={<InfoCircleIcon />}
size='small'
@ -110,7 +111,7 @@ const FileList = ({
}}
/>
)}
{editing && (
{editing && !minimal && (
<Button
icon={<BinIcon />}
size='small'
@ -120,7 +121,7 @@ const FileList = ({
)}
</Flex>
</Flex>
{previewOpen ? (
{previewOpen && !minimal ? (
<>
<Divider style={{ marginTop: 0, marginBottom: card ? 0 : '4px' }} />
<FilePreview file={file} style={{ width: '100%' }} />
@ -174,7 +175,8 @@ FileList.propTypes = {
showInfo: PropTypes.bool,
showDownload: PropTypes.bool,
defaultPreviewOpen: PropTypes.bool,
card: PropTypes.bool
card: PropTypes.bool,
minimal: PropTypes.bool
}
export default FileList

View File

@ -2,7 +2,14 @@ import { Upload, Button, Flex, Typography, Space, Progress, Card } from 'antd'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../context/ApiServerContext'
import UploadIcon from '../../Icons/UploadIcon'
import { useContext, useState, useEffect } from 'react'
import {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState
} from 'react'
import ObjectSelect from './ObjectSelect'
import FileList from './FileList'
import PlusIcon from '../../Icons/PlusIcon'
@ -11,6 +18,16 @@ import FileIcon from '../../Icons/FileIcon'
const { Text } = Typography
const getFileIdentity = (value, multiple) => {
if (multiple) {
return Array.isArray(value)
? value.map((file) => file?._id || file).join(',')
: ''
}
return value?._id || value || ''
}
const FileUpload = ({
value,
onChange,
@ -23,6 +40,11 @@ const FileUpload = ({
const { uploadFile } = useContext(ApiServerContext)
const [uploading, setUploading] = useState(false)
const [uploadProgress, setUploadProgress] = useState(0)
const onChangeRef = useRef(onChange)
useEffect(() => {
onChangeRef.current = onChange
}, [onChange])
// Track current files using useState
const [currentFiles, setCurrentFiles] = useState(() => {
@ -35,29 +57,34 @@ const FileUpload = ({
// Update currentFiles when value prop changes
useEffect(() => {
if (multiple) {
setCurrentFiles(Array.isArray(value) ? value : [])
} else {
setCurrentFiles(value || null)
}
}, [value, multiple])
setCurrentFiles((prev) => {
if (
getFileIdentity(prev, multiple) === getFileIdentity(value, multiple)
) {
return prev
}
// Track if there are no items in the list
const [hasNoItems, setHasNoItems] = useState(false)
return multiple ? (Array.isArray(value) ? value : []) : value || null
})
}, [value, multiple])
// Track the selected file from ObjectSelect
const [selectedFile, setSelectedFile] = useState(null)
// Update hasNoItems when currentFiles changes
useEffect(() => {
const noItems = multiple
? !currentFiles ||
!Array.isArray(currentFiles) ||
currentFiles.length === 0
: !currentFiles
setHasNoItems(noItems)
onChange(currentFiles)
}, [currentFiles, multiple, onChange])
const hasNoItems = useMemo(
() =>
multiple
? !currentFiles ||
!Array.isArray(currentFiles) ||
currentFiles.length === 0
: !currentFiles,
[currentFiles, multiple]
)
const updateCurrentFiles = useCallback((nextFiles) => {
setCurrentFiles(nextFiles)
onChangeRef.current?.(nextFiles)
}, [])
const handleFileUpload = async (file) => {
try {
@ -70,10 +97,10 @@ const FileUpload = ({
if (multiple) {
// For multiple files, add to existing array
const newFiles = [...currentFiles, uploadedFile]
setCurrentFiles(newFiles)
updateCurrentFiles(newFiles)
} else {
// For single file, replace the value
setCurrentFiles(uploadedFile)
updateCurrentFiles(uploadedFile)
}
}
} catch (error) {
@ -88,10 +115,10 @@ const FileUpload = ({
if (multiple) {
// For multiple files, add to existing array
const newFiles = [...currentFiles, selectedFile]
setCurrentFiles(newFiles)
updateCurrentFiles(newFiles)
} else {
// For single file, replace the value
setCurrentFiles(selectedFile)
updateCurrentFiles(selectedFile)
}
// Clear the selection
setSelectedFile(null)
@ -110,7 +137,7 @@ const FileUpload = ({
return (
<Flex gap={'small'} vertical>
{hasNoItems && uploading == false ? (
<Flex gap={'small'} align='center'>
<Flex gap={'small'} align='center' wrap>
<Space.Compact style={{ flexGrow: 1 }}>
<ObjectSelect
type={'file'}
@ -168,7 +195,7 @@ const FileUpload = ({
showPreview={showPreview}
defaultPreviewOpen={defaultPreviewOpen}
onChange={(updatedFiles) => {
setCurrentFiles(updatedFiles)
updateCurrentFiles(updatedFiles)
}}
/>
</Flex>

View File

@ -1,6 +1,6 @@
import * as GCodePreview from 'gcode-preview'
import PropTypes from 'prop-types'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef } from 'react'
import * as THREE from 'three'
function GCodePreviewUI(props) {
@ -14,33 +14,36 @@ function GCodePreviewUI(props) {
style = {}
} = props
const canvasRef = useRef(null)
const [preview, setPreview] = useState()
const previewRef = useRef(null)
const resizePreview = useCallback(() => {
preview?.resize()
}, [preview])
previewRef.current?.resize()
}, [])
// Ex-ref methods removed; this component is now a regular functional component
useEffect(() => {
setPreview(
GCodePreview.init({
canvas: canvasRef.current,
startLayer,
endLayer,
lineWidth,
topLayerColor: new THREE.Color(topLayerColor).getHex(),
lastSegmentColor: new THREE.Color(lastSegmentColor).getHex(),
buildVolume: { x: 250, y: 220, z: 150 },
initialCameraPosition: [0, 400, 450],
allowDragNDrop: false
})
)
if (!canvasRef.current) return
previewRef.current?.dispose?.()
previewRef.current = GCodePreview.init({
canvas: canvasRef.current,
startLayer,
endLayer,
lineWidth,
topLayerColor: new THREE.Color(topLayerColor).getHex(),
lastSegmentColor: new THREE.Color(lastSegmentColor).getHex(),
buildVolume: { x: 250, y: 220, z: 150 },
initialCameraPosition: [0, 400, 450],
allowDragNDrop: false
})
window.addEventListener('resize', resizePreview)
return () => {
window.removeEventListener('resize', resizePreview)
previewRef.current?.dispose?.()
previewRef.current = null
}
}, [
endLayer,
@ -52,18 +55,27 @@ function GCodePreviewUI(props) {
])
useEffect(() => {
let cancelled = false
const loadFromSrc = async () => {
const preview = previewRef.current
if (!src || !preview) return
try {
const response = await fetch(src)
const text = await response.text()
if (cancelled || previewRef.current !== preview) return
preview.processGCode(text)
} catch (e) {
if (cancelled) return
console.error('Failed to load G-code from src', e)
}
}
loadFromSrc()
}, [src, preview])
return () => {
cancelled = true
}
}, [endLayer, lastSegmentColor, lineWidth, src, startLayer, topLayerColor])
return <canvas ref={canvasRef} style={style}></canvas>
}

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useContext, useCallback } from 'react'
import { useState, useEffect, useContext, useCallback, useRef } from 'react'
import { Form } from 'antd'
import { ApiServerContext } from '../context/ApiServerContext'
import { useMessageContext } from '../context/MessageContext'
@ -38,10 +38,35 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
const [submitLoading, setSubmitLoading] = useState(false)
const [formValid, setFormValid] = useState(false)
const [form] = Form.useForm()
const validationRunRef = useRef(0)
const formUpdateValues = Form.useWatch([], form)
const { showSuccess, showError: showMessageError } = useMessageContext()
const { createObject, showError } = useContext(ApiServerContext)
const validateForm = useCallback(() => {
const validationRun = ++validationRunRef.current
let cancelled = false
const timeoutId = setTimeout(() => {
form
.validateFields({ validateOnly: true })
.then(() => {
if (!cancelled && validationRun === validationRunRef.current) {
setFormValid(true)
}
})
.catch(() => {
if (!cancelled && validationRun === validationRunRef.current) {
setFormValid(false)
}
})
}, 0)
return () => {
cancelled = true
clearTimeout(timeoutId)
}
}, [form])
// Get the model definition for this object type
const model = getModelByName(type)
@ -141,21 +166,15 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
const computedValuesObject = buildObjectFromEntries(computedEntries)
const initialFormData = merge({}, defaultValues, computedValuesObject)
form.setFieldsValue(initialFormData)
form
.validateFields({ validateOnly: true })
.then(() => setFormValid(true))
.catch(() => setFormValid(false))
setObjectData((prev) => merge({}, prev, initialFormData))
return validateForm()
}
}, [form, defaultValues, calculateComputedValues, model])
}, [form, defaultValues, calculateComputedValues, model, validateForm])
// Validate form on change
useEffect(() => {
form
.validateFields({ validateOnly: true })
.then(() => setFormValid(true))
.catch(() => setFormValid(false))
}, [form, formUpdateValues])
return validateForm()
}, [validateForm, formUpdateValues])
const handleSubmit = async () => {
try {
@ -217,7 +236,7 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
}}
>
{children({
submitLoading: submitLoading,
submitLoading,
handleSubmit,
form,
formValid,

View File

@ -103,11 +103,10 @@ const NoteItem = ({
if (isExpanded == true) {
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
'note',
(noteData) => {
if (noteData.parent._id == note._id) {
if (isExpanded == true) {
handleNoteExpand()
}
{ 'parent._id': note._id },
() => {
if (isExpanded == true) {
handleNoteExpand()
}
}
)

View File

@ -95,11 +95,8 @@ const NotesPanel = ({ _id, type }) => {
if (connected == true && subscribeToObjectTypeUpdatesRef.current == null) {
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
'note',
(noteData) => {
if (noteData.parent._id == _id) {
handleReloadData()
}
}
{ 'parent._id': _id },
() => handleReloadData()
)
}
return () => {

View File

@ -99,6 +99,14 @@ const ObjectProperty = ({
value = value(objectData)
}
if (max && typeof max == 'function' && objectData) {
max = max(objectData)
}
if (min && typeof min == 'function' && objectData) {
min = min(objectData)
}
if (objectType && typeof objectType == 'function' && objectData) {
objectType = objectType(objectData)
}

View File

@ -17,6 +17,8 @@ import merge from 'lodash/merge'
import { getModelProperty } from '../../../database/ObjectModels'
const { SHOW_CHILD } = TreeSelect
const EMPTY_OBJECT = {}
// Helper to check if two values are equal (handling objects/ids)
const areValuesEqual = (v1, v2) => {
const id1 = v1 && typeof v1 === 'object' && v1._id ? v1._id : v1
@ -28,10 +30,11 @@ const ObjectSelect = ({
type = 'unknown',
showSearch = false,
multiple = false,
treeSelectProps = {},
filter = {},
masterFilter = {},
treeSelectProps = EMPTY_OBJECT,
filter = EMPTY_OBJECT,
masterFilter = EMPTY_OBJECT,
value,
onChange,
disabled = false,
...rest
}) => {
@ -55,11 +58,6 @@ const ObjectSelect = ({
const prevValueRef = useRef(value)
const isInternalChangeRef = useRef(false)
useEffect(() => {
console.log('type', type)
console.log('value', value)
}, [value, type])
// Normalize a value to an identity string so we can detect in-place _id updates
const getValueIdentity = useCallback((val) => {
if (val && typeof val === 'object') {
@ -359,15 +357,15 @@ const ObjectSelect = ({
.filter(Boolean)
}
setTreeSelectValue(value)
if (rest.onChange) rest.onChange(selectedObjects)
onChange?.(selectedObjects)
} else {
// Single selection
const selectedObject = objectList.find((obj) => obj._id === value)
setTreeSelectValue(value)
if (rest.onChange) rest.onChange(selectedObject)
onChange?.(selectedObject)
}
},
[multiple, objectList, rest]
[multiple, objectList, onChange]
)
// Update treeData when objectPropertiesTree changes

View File

@ -438,9 +438,21 @@ const ObjectTable = forwardRef(
// Store the latest updateEventHandler in a ref
updateEventHandlerRef.current = updateEventHandler
const newEventHandler = useCallback(() => {
reload()
}, [reload])
const newEventHandler = useCallback(
(params) => {
logger.debug('New event handler:', params)
reload()
},
[reload]
)
const subscriptionFilter = useMemo(() => {
const active = {}
Object.entries(sidebarFilter).forEach(([k, v]) => {
if (v !== '' && v !== undefined) active[k] = v
})
return { ...active, ...masterFilter }
}, [sidebarFilter, masterFilter])
// Subscribe to real-time updates for all items
useEffect(() => {
@ -513,16 +525,28 @@ const ObjectTable = forwardRef(
}, [connected])
useEffect(() => {
if (
connected == true &&
subscribeToObjectTypeUpdatesRef.current == null
) {
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
if (connected == true) {
const unsubscribe = subscribeToObjectTypeUpdates(
type,
subscriptionFilter,
newEventHandler
)
subscribeToObjectTypeUpdatesRef.current = unsubscribe
return () => {
if (unsubscribe) unsubscribe()
if (subscribeToObjectTypeUpdatesRef.current === unsubscribe) {
subscribeToObjectTypeUpdatesRef.current = null
}
}
}
}, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])
}, [
type,
subscriptionFilter,
subscribeToObjectTypeUpdates,
connected,
newEventHandler
])
const updateData = useCallback(
(id, updatedData) => {
@ -659,11 +683,13 @@ const ObjectTable = forwardRef(
}
})
console.log('filters--', filters)
setSidebarFilter(next)
setPages([])
setLoading(true)
loadPage(initialPage, getActiveFilter(next), {
field: sorter.field,
field: sorter.columnKey,
order: sorter.order
})
}
@ -854,29 +880,42 @@ const ObjectTable = forwardRef(
style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
ref={cardsContainerRef}
>
{tableData.map((record) => (
<Col xs={24} sm={12} md={12} lg={8} xl={6} xxl={6} key={record._id}>
<div style={{ width: '100%', overflow: 'hidden' }}>
<RowForm
record={record}
isEditing={isEditing}
onRegister={registerForm}
>
<Flex align={'center'} vertical gap={'middle'}>
<ObjectCard
model={model}
modelProperties={modelProperties}
visibleColumns={visibleColumns}
record={record}
isEditing={isEditing}
rowActions={rowActions}
renderActions={renderActions}
/>
</Flex>
</RowForm>
</div>
</Col>
))}
{tableData.map((record) => {
if (record?._id == undefined) {
return null
}
return (
<Col
xs={24}
sm={12}
md={12}
lg={8}
xl={6}
xxl={6}
key={record._id}
>
<div style={{ width: '100%', overflow: 'hidden' }}>
<RowForm
record={record}
isEditing={isEditing}
onRegister={registerForm}
>
<Flex align={'center'} vertical gap={'middle'}>
<ObjectCard
model={model}
modelProperties={modelProperties}
visibleColumns={visibleColumns}
record={record}
isEditing={isEditing}
rowActions={rowActions}
renderActions={renderActions}
/>
</Flex>
</RowForm>
</div>
</Col>
)
})}
</Row>
)
}

View File

@ -136,6 +136,10 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
status = 'cyan'
text = 'Ordered'
break
case 'posted':
status = 'magenta'
text = 'Posted'
break
case 'received':
status = 'success'
text = 'Received'

View File

@ -17,6 +17,7 @@ import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import config from '../../../config'
import loglevel from 'loglevel'
import { getModelByName } from '../../../database/ObjectModels'
const logger = loglevel.getLogger('ApiServerContext')
logger.setLevel(config.logLevel)
@ -25,6 +26,35 @@ const SPOTLIGHT_CACHE_TTL_MS = 10_000
const spotlightCache = new Map()
const runningSpotlightFetches = new Map()
const stableStringify = (value) => {
if (Array.isArray(value)) {
return `[${value.map(stableStringify).join(',')}]`
}
if (value && typeof value === 'object') {
return `{${Object.keys(value)
.sort()
.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
.join(',')}}`
}
return JSON.stringify(value)
}
const getObjectTypeSubscriptionKey = (objectType, filter = {}) =>
`${objectType}:${stableStringify(filter || {})}`
const getObjectTypeSubscriptionArgs = (filterOrCallback, callback) => {
if (typeof filterOrCallback === 'function') {
return { filter: {}, callback: filterOrCallback }
}
return { filter: filterOrCallback || {}, callback }
}
const getObjectEndpoint = (type) =>
getModelByName(type)?.endpoint || `${type.toLowerCase()}s`
const ApiServerContext = createContext()
const ApiServerProvider = ({ children }) => {
@ -316,12 +346,16 @@ const ApiServerProvider = ({ children }) => {
const handleObjectNew = async (data) => {
logger.debug('Notifying object new:', data)
const objectType = data.objectType || 'unknown'
const callbacksRefKey = getObjectTypeSubscriptionKey(
objectType,
data.filter || {}
)
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
const callbacks = subscribedCallbacksRef.current.get(objectType)
if (objectType && subscribedCallbacksRef.current.has(callbacksRefKey)) {
const callbacks = subscribedCallbacksRef.current.get(callbacksRefKey)
logger.debug(
`Calling ${callbacks.length} callbacks for type:`,
objectType
callbacksRefKey
)
callbacks.forEach((callback) => {
try {
@ -332,7 +366,7 @@ const ApiServerProvider = ({ children }) => {
})
} else {
logger.debug(
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
`No callbacks found for object: ${callbacksRefKey}, subscribed callbacks:`,
Array.from(subscribedCallbacksRef.current.keys())
)
}
@ -341,12 +375,16 @@ const ApiServerProvider = ({ children }) => {
const handleObjectDelete = async (data) => {
logger.debug('Notifying object delete:', data)
const objectType = data.objectType || 'unknown'
const callbacksRefKey = getObjectTypeSubscriptionKey(
objectType,
data.filter || {}
)
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
const callbacks = subscribedCallbacksRef.current.get(objectType)
if (objectType && subscribedCallbacksRef.current.has(callbacksRefKey)) {
const callbacks = subscribedCallbacksRef.current.get(callbacksRefKey)
logger.debug(
`Calling ${callbacks.length} callbacks for type:`,
objectType
callbacksRefKey
)
callbacks.forEach((callback) => {
try {
@ -357,7 +395,7 @@ const ApiServerProvider = ({ children }) => {
})
} else {
logger.debug(
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
`No callbacks found for object: ${callbacksRefKey}, subscribed callbacks:`,
Array.from(subscribedCallbacksRef.current.keys())
)
}
@ -389,24 +427,29 @@ const ApiServerProvider = ({ children }) => {
}
}, [])
const offObjectTypeUpdatesEvent = useCallback((objectType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
// Remove callback from the subscribed callbacks map
if (subscribedCallbacksRef.current.has(objectType)) {
const callbacks = subscribedCallbacksRef.current
.get(objectType)
.filter((cb) => cb !== callback)
if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(objectType)
socketRef.current.emit('unsubscribeObjectTypeUpdate', {
objectType: objectType
})
} else {
subscribedCallbacksRef.current.set(objectType, callbacks)
const offObjectTypeUpdatesEvent = useCallback(
(objectType, filter, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = getObjectTypeSubscriptionKey(objectType, filter)
// Remove callback from the subscribed callbacks map
if (subscribedCallbacksRef.current.has(callbacksRefKey)) {
const callbacks = subscribedCallbacksRef.current
.get(callbacksRefKey)
.filter((cb) => cb !== callback)
if (callbacks.length === 0) {
subscribedCallbacksRef.current.delete(callbacksRefKey)
socketRef.current.emit('unsubscribeObjectTypeUpdate', {
objectType: objectType,
filter: filter
})
} else {
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
}
}
}
}
}, [])
},
[]
)
const subscribeToObjectUpdates = useCallback(
(id, objectType, callback) => {
@ -471,31 +514,46 @@ const ApiServerProvider = ({ children }) => {
}, [connected, userProfile?._id, subscribeToObjectUpdates, setUserProfile])
const subscribeToObjectTypeUpdates = useCallback(
(objectType, callback) => {
logger.debug('Subscribing to type updates:', objectType)
(objectType, filterOrCallback = {}, maybeCallback) => {
const { filter, callback } = getObjectTypeSubscriptionArgs(
filterOrCallback,
maybeCallback
)
const callbacksRefKey = getObjectTypeSubscriptionKey(objectType, filter)
logger.debug('Subscribing to type updates:', objectType, filter)
if (socketRef.current && socketRef.current.connected == true) {
// Add callback to the subscribed callbacks map immediately
if (!subscribedCallbacksRef.current.has(objectType)) {
subscribedCallbacksRef.current.set(objectType, [])
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
subscribedCallbacksRef.current.set(callbacksRefKey, [])
}
subscribedCallbacksRef.current.get(objectType).push(callback)
logger.debug(
`Added callback for type ${objectType}, total callbacks: ${subscribedCallbacksRef.current.get(objectType).length}`
)
socketRef.current.emit(
'subscribeToObjectTypeUpdate',
{ objectType: objectType },
(result) => {
if (result.success) {
logger.info('Subscribed to objectType:', objectType)
const callbacksLength =
subscribedCallbacksRef.current.get(callbacksRefKey).length
if (callbacksLength <= 0) {
socketRef.current.emit(
'subscribeToObjectTypeUpdate',
{ objectType: objectType, filter: filter },
(result) => {
if (result.success) {
logger.info('Subscribed to objectType:', objectType, filter)
}
}
}
)
}
subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
logger.debug(
`Added callback for type ${callbacksRefKey}, total callbacks: ${callbacksLength + 1}`
)
logger.debug(
'Registered type event listener for object:',
callbacksRefKey
)
logger.debug('Registered type event listener for object:', objectType)
// Return cleanup function
return () => offObjectTypeUpdatesEvent(objectType, callback)
return () => offObjectTypeUpdatesEvent(objectType, filter, callback)
}
},
[offObjectTypeUpdatesEvent]
@ -708,7 +766,7 @@ const ApiServerProvider = ({ children }) => {
// Generalized fetchObject function
const fetchObject = async (id, type) => {
const fetchUrl = `${config.backendUrl}/${type}s/${id}`
const fetchUrl = `${config.backendUrl}/${getObjectEndpoint(type)}/${id}`
setFetchLoading(true)
logger.debug('Fetching from ' + fetchUrl)
try {
@ -738,21 +796,37 @@ const ApiServerProvider = ({ children }) => {
sorter = {},
onDataChange
} = params
let newFilter = { ...filter }
if (filter != null && Object.keys(filter).length > 0) {
const model = getModelByName(type)
for (const key of Object.keys(filter)) {
const property = model?.properties?.find((p) => p.name === key)
if (property && property.type === 'object') {
const value = filter[key]
newFilter[`${key}._id`] = value?._id ?? value
delete newFilter[key]
}
}
}
logger.debug('Fetching table data from:', type, {
page,
limit,
filter,
newFilter,
sorter
})
try {
const response = await axios.get(
`${config.backendUrl}/${type.toLowerCase()}s`,
`${config.backendUrl}/${getObjectEndpoint(type)}`,
{
params: {
page,
limit,
...filter,
...newFilter,
sort: sorter.field,
order: sorter.order
},
@ -799,7 +873,7 @@ const ApiServerProvider = ({ children }) => {
try {
const response = await axios.get(
`${config.backendUrl}/${type.toLowerCase()}s/properties`,
`${config.backendUrl}/${getObjectEndpoint(type)}/properties`,
{
params: {
...Object.keys(filter).reduce((acc, key) => {
@ -833,7 +907,7 @@ const ApiServerProvider = ({ children }) => {
// Update filament information
const updateObject = async (id, type, value) => {
const updateUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
const updateUrl = `${config.backendUrl}/${getObjectEndpoint(type)}/${id}`
logger.debug('Updating info for ' + id)
try {
const response = await axios.put(updateUrl, value, {
@ -855,7 +929,7 @@ const ApiServerProvider = ({ children }) => {
// Update multiple objects
const updateMultipleObjects = async (type, objects) => {
const updateUrl = `${config.backendUrl}/${type.toLowerCase()}s`
const updateUrl = `${config.backendUrl}/${getObjectEndpoint(type)}`
logger.debug('Updating multiple objects for ' + type)
try {
const response = await axios.put(updateUrl, objects, {
@ -877,7 +951,7 @@ const ApiServerProvider = ({ children }) => {
// Update filament information
const deleteObject = async (id, type) => {
const deleteUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
const deleteUrl = `${config.backendUrl}/${getObjectEndpoint(type)}/${id}`
logger.debug('Deleting object ID: ' + id)
try {
const response = await axios.delete(deleteUrl, {
@ -899,7 +973,7 @@ const ApiServerProvider = ({ children }) => {
// Update filament information
const createObject = async (type, value) => {
const createUrl = `${config.backendUrl}/${type.toLowerCase()}s`
const createUrl = `${config.backendUrl}/${getObjectEndpoint(type)}`
logger.debug('Creating object...')
try {
const response = await axios.post(createUrl, value, {
@ -920,7 +994,7 @@ const ApiServerProvider = ({ children }) => {
// Call a function on an object
const sendObjectFunction = async (id, type, functionName, value = {}) => {
const url = `${config.backendUrl}/${type.toLowerCase()}s/${id}/${functionName}`
const url = `${config.backendUrl}/${getObjectEndpoint(type)}/${id}/${functionName}`
logger.debug(`Calling object function ${functionName} for ${id} at ${url}`)
try {
const response = await axios.post(url, value, {
@ -940,7 +1014,7 @@ const ApiServerProvider = ({ children }) => {
}
const getObjectFunction = async (id, type, functionName, params = {}) => {
const url = `${config.backendUrl}/${type.toLowerCase()}s/${id}/${functionName}`
const url = `${config.backendUrl}/${getObjectEndpoint(type)}/${id}/${functionName}`
logger.debug(`Fetching object function ${functionName} for ${id} at ${url}`)
try {
const response = await axios.get(url, {
@ -1165,7 +1239,7 @@ const ApiServerProvider = ({ children }) => {
if (objectType === 'history') {
statsUrl = `${config.backendUrl}/stats/history`
} else {
statsUrl = `${config.backendUrl}/${objectType.toLowerCase()}s/stats`
statsUrl = `${config.backendUrl}/${getObjectEndpoint(objectType)}/stats`
}
const response = await axios.get(statsUrl, {
@ -1190,7 +1264,7 @@ const ApiServerProvider = ({ children }) => {
const encodedStartDate = encodeURIComponent(startDate.toISOString())
const encodedEndDate = encodeURIComponent(endDate.toISOString())
try {
const historyUrl = `${config.backendUrl}/${objectType.toLowerCase()}s/history?from=${encodedStartDate}&to=${encodedEndDate}`
const historyUrl = `${config.backendUrl}/${getObjectEndpoint(objectType)}/history?from=${encodedStartDate}&to=${encodedEndDate}`
const response = await axios.get(historyUrl, {
headers: {

View File

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

View File

@ -7,6 +7,7 @@ import { Spool } from './models/Spool'
import { GCodeFile } from './models/GCodeFile'
import { Job } from './models/Job'
import { Product } from './models/Product'
import { ProductCategory } from './models/ProductCategory'
import { ProductSku } from './models/ProductSku'
import { Part } from './models/Part.js'
import { PartSku } from './models/PartSku.js'
@ -56,6 +57,7 @@ export const objectModels = [
GCodeFile,
Job,
Product,
ProductCategory,
ProductSku,
Part,
PartSku,
@ -106,6 +108,7 @@ export {
GCodeFile,
Job,
Product,
ProductCategory,
ProductSku,
Part,
PartSku,

View File

@ -1,6 +1,5 @@
import AppPasswordIcon from '../../components/Icons/AppPasswordIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
@ -21,13 +20,6 @@ export const AppPassword = {
url: (_id) =>
`/dashboard/management/apppasswords/info?appPasswordId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/apppasswords/info?appPasswordId=${_id}&action=reload`
},
{
name: 'edit',

View File

@ -3,7 +3,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const Client = {
@ -20,13 +19,6 @@ export const Client = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/sales/clients/info?clientId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/sales/clients/info?clientId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -3,7 +3,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const Courier = {
@ -20,13 +19,6 @@ export const Courier = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/couriers/info?courierId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/couriers/info?courierId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -3,7 +3,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const CourierService = {
@ -21,13 +20,6 @@ export const CourierService = {
url: (_id) =>
`/dashboard/management/courierservices/info?courierServiceId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/courierservices/info?courierServiceId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -1,5 +1,4 @@
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
@ -22,13 +21,6 @@ export const DocumentJob = {
`/dashboard/management/documentjobs/info?documentJobId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/documentjobs/info?documentJobId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -1,5 +1,4 @@
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EditIcon from '../../components/Icons/EditIcon'
import DocumentPrinterIcon from '../../components/Icons/DocumentPrinterIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
@ -21,13 +20,6 @@ export const DocumentPrinter = {
`/dashboard/management/documentprinters/info?documentPrinterId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/documentprinters/info?documentPrinterId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -1,5 +1,4 @@
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
@ -21,13 +20,6 @@ export const DocumentSize = {
`/dashboard/management/documentsizes/info?documentSizeId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/documentsizes/info?documentSizeId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -1,5 +1,4 @@
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
@ -30,13 +29,6 @@ export const DocumentTemplate = {
`/dashboard/management/documenttemplates/info?documentTemplateId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/documenttemplates/info?documentTemplateId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -1,7 +1,6 @@
import EditIcon from '../../components/Icons/EditIcon'
import FilamentIcon from '../../components/Icons/FilamentIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
@ -21,13 +20,6 @@ export const Filament = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/filaments/info?filamentId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/filaments/info?filamentId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -4,7 +4,6 @@ import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
export const FilamentSku = {
name: 'filamentSku',
@ -21,13 +20,6 @@ export const FilamentSku = {
url: (_id) =>
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',
@ -102,6 +94,7 @@ export const FilamentSku = {
'createdAt',
'updatedAt'
],
group: ['filament'],
properties: [
{
name: '_id',

View File

@ -22,14 +22,15 @@ export const FilamentStock = {
'state',
'currentWeight',
'startingWeight',
'filament',
'filamentSku',
'stockLocation',
'createdAt',
'updatedAt'
],
filters: ['_id'],
sorters: ['createdAt', 'updatedAt'],
group: ['filamentSku'],
filters: ['_id', 'filament', 'filament._id', 'filamentSku', 'filamentSku._id'],
sorters: ['createdAt', 'updatedAt', 'filament', 'filamentSku'],
group: ['filament', 'filamentSku'],
properties: [
{
name: '_id',
@ -71,6 +72,17 @@ export const FilamentStock = {
readOnly: true,
columnWidth: 175
},
{
name: 'filament',
label: 'Filament',
type: 'object',
value: null,
objectType: 'filament',
required: true,
showHyperlink: true,
columnWidth: 200,
initial: true
},
{
name: 'filamentSku',
label: 'Filament SKU',
@ -80,7 +92,12 @@ export const FilamentStock = {
initial: true,
required: true,
showHyperlink: true,
columnWidth: 200
columnWidth: 200,
masterFilter: (objectData) => {
return {
filament: objectData?.filament?._id
}
}
},
{
name: 'stockLocation',

View File

@ -4,7 +4,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const File = {
@ -21,13 +20,6 @@ export const File = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/files/info?fileId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/files/info?fileId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',
@ -78,7 +70,15 @@ export const File = {
}
],
url: (id) => `/dashboard/management/files/info?fileId=${id}`,
columns: ['_reference', 'name', 'type', 'size', 'temp', 'createdAt'],
columns: [
'_reference',
'name',
'type',
'size',
'temp',
'createdAt',
'updatedAt'
],
filters: ['name', '_id', 'type', 'temp'],
sorters: ['name', 'type', 'size', 'createdAt', 'temp'],
group: ['type'],
@ -129,7 +129,7 @@ export const File = {
type: 'text',
readOnly: true,
required: true,
columnWidth: 120
columnWidth: 190
},
{
name: 'size',

View File

@ -4,7 +4,7 @@ import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import GCodeFileIcon from '../../components/Icons/GCodeFileIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EyeIcon from '../../components/Icons/EyeIcon'
export const GCodeFile = {
name: 'gcodeFile',
@ -12,6 +12,14 @@ export const GCodeFile = {
prefix: 'GCF',
icon: GCodeFileIcon,
actions: [
{
name: 'preview',
label: 'Preview',
row: true,
icon: EyeIcon,
url: (_id) =>
`/dashboard/production/gcodefiles/preview?gcodeFileId=${_id}`
},
{
name: 'info',
label: 'Info',
@ -20,13 +28,6 @@ export const GCodeFile = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/production/gcodefiles/info?gcodeFileId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/production/gcodefiles/info?gcodeFileId=${_id}&action=reload`
},
{
name: 'download',
label: 'Download',
@ -71,6 +72,7 @@ export const GCodeFile = {
columns: [
'_reference',
'name',
'filament',
'filamentSku',
'cost',
'gcodeFileInfo.estimatedPrintingTimeNormalMode',
@ -80,9 +82,24 @@ export const GCodeFile = {
'gcodeFileInfo.hotPlateTemp',
'updatedAt'
],
filters: ['_id', 'name', 'cost', 'updatedAt'],
sorters: ['name', 'cost', 'createdAt', 'updatedAt'],
group: ['filamentSku'],
filters: [
'_id',
'name',
'filament',
'filament._id',
'filamentSku',
'cost',
'updatedAt'
],
sorters: [
'name',
'filament',
'filamentSku',
'cost',
'createdAt',
'updatedAt'
],
group: ['filament', 'filamentSku'],
properties: [
{
name: '_id',
@ -139,6 +156,16 @@ export const GCodeFile = {
filter: ['.gcode', '.g'],
columnWidth: 200
},
{
name: 'filament',
label: 'Filament',
type: 'object',
value: null,
objectType: 'filament',
required: true,
showHyperlink: true,
columnWidth: 200
},
{
name: 'filamentSku',
label: 'Filament SKU',
@ -147,7 +174,12 @@ export const GCodeFile = {
objectType: 'filamentSku',
required: true,
showHyperlink: true,
columnWidth: 200
columnWidth: 200,
masterFilter: (objectData) => {
return {
filament: objectData?.filament?._id
}
}
},
{
name: 'cost',
@ -156,9 +188,9 @@ export const GCodeFile = {
roundNumber: 2,
value: (objectData) => {
const fs = objectData?.filamentSku
const costPerKg =
fs?.overrideCost ? fs?.cost : fs?.filament?.cost
if (!costPerKg || !objectData?.file?.metaData?.filamentUsedG) return undefined
const costPerKg = fs?.overrideCost ? fs?.cost : fs?.filament?.cost
if (!costPerKg || !objectData?.file?.metaData?.filamentUsedG)
return undefined
return objectData.file.metaData.filamentUsedG * (costPerKg / 1000)
},
readOnly: true,
@ -238,6 +270,7 @@ export const GCodeFile = {
label: 'Parts',
type: 'objectChildren',
objectType: 'part',
size: 'medium',
properties: [
{
name: 'part',

View File

@ -1,9 +1,9 @@
import HostIcon from '../../components/Icons/HostIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
import OTPIcon from '../../components/Icons/OTPIcon'
export const Host = {
@ -21,13 +21,6 @@ export const Host = {
url: (_id) => `/dashboard/management/hosts/info?hostId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/hosts/info?hostId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',
@ -68,6 +61,14 @@ export const Host = {
icon: OTPIcon,
url: (_id) =>
`/dashboard/management/hosts/info?hostId=${_id}&action=hostOTP`
},
{
name: 'delete',
label: 'Delete',
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/management/hosts/info?hostId=${_id}&action=delete`
}
],
columns: ['_reference', 'name', 'state', 'tags', 'connectedAt'],

View File

@ -1,6 +1,5 @@
import JobIcon from '../../components/Icons/JobIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import dayjs from 'dayjs'
@ -30,12 +29,6 @@ export const Job = {
return objectData?.state?.type != 'draft'
}
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) => `/dashboard/production/jobs/info?jobId=${_id}&action=reload`
}
],
columns: ['_reference', 'quantity', 'state', 'gcodeFile', 'createdAt'],
filters: ['state', '_id', 'gcodeFile', 'quantity'],

View File

@ -3,7 +3,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
@ -21,13 +20,6 @@ export const Listing = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/sales/listings/info?listingId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/sales/listings/info?listingId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -3,7 +3,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const ListingVarient = {
@ -20,13 +19,6 @@ export const ListingVarient = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/sales/listingvarients/info?listingVarientId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/sales/listingvarients/info?listingVarientId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -21,13 +21,6 @@ export const Marketplace = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/sales/marketplaces/info?marketplaceId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/sales/marketplaces/info?marketplaceId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -3,7 +3,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const Material = {
@ -20,13 +19,6 @@ export const Material = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/materials/info?materialId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/materials/info?materialId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -1,6 +1,5 @@
import NoteTypeIcon from '../../components/Icons/NoteTypeIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
@ -19,13 +18,6 @@ export const NoteType = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/notetypes/info?noteTypeId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/notetypes/info?noteTypeId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -230,7 +230,7 @@ export const OrderItem = {
},
{
name: 'sku',
label: 'SKU',
label: 'Item SKU',
type: 'object',
objectType: (objectData) => {
if (objectData?.itemType === 'filament') return 'filamentSku'
@ -238,11 +238,9 @@ export const OrderItem = {
if (objectData?.itemType === 'product') return 'productSku'
return undefined
},
required: false,
required: true,
showHyperlink: true,
columnWidth: 300,
visible: (objectData) =>
['filament', 'part', 'product'].includes(objectData?.itemType),
masterFilter: (objectData) => {
console.log(objectData)
if (objectData?.itemType === 'filament' && objectData?.item?._id) {

View File

@ -1,7 +1,6 @@
import EditIcon from '../../components/Icons/EditIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import PartIcon from '../../components/Icons/PartIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
@ -20,13 +19,6 @@ export const Part = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/parts/info?partId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/parts/info?partId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -4,7 +4,6 @@ import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
export const PartSku = {
name: 'partSku',
@ -20,13 +19,6 @@ export const PartSku = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/partskus/info?partSkuId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/partskus/info?partSkuId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -26,13 +26,6 @@ export const Printer = {
url: (_id) => `/dashboard/production/printers/info?printerId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/production/printers/info?printerId=${_id}&action=reload`
},
{
name: 'control',
label: 'Control',

View File

@ -1,6 +1,5 @@
import ProductIcon from '../../components/Icons/ProductIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
@ -20,13 +19,6 @@ export const Product = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/products/info?productId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/products/info?productId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',
@ -76,6 +68,7 @@ export const Product = {
columns: [
'_reference',
'name',
'productCategory',
'tags',
'vendor',
'cost',
@ -85,8 +78,30 @@ export const Product = {
'createdAt',
'updatedAt'
],
filters: ['_id', 'name', 'type', 'color', 'vendor', 'cost', 'costWithTax', 'price', 'priceWithTax'],
sorters: ['name', 'createdAt', 'type', 'vendor', 'cost', 'costWithTax', 'price', 'priceWithTax', 'updatedAt'],
filters: [
'_id',
'name',
'productCategory',
'type',
'color',
'vendor',
'cost',
'costWithTax',
'price',
'priceWithTax'
],
sorters: [
'name',
'productCategory',
'createdAt',
'type',
'vendor',
'cost',
'costWithTax',
'price',
'priceWithTax',
'updatedAt'
],
properties: [
{
name: '_id',
@ -128,6 +143,15 @@ export const Product = {
readOnly: true,
columnWidth: 175
},
{
name: 'productCategory',
label: 'Product Category',
required: true,
type: 'object',
objectType: 'productCategory',
showHyperlink: true,
columnWidth: 200
},
{
name: 'vendor',
label: 'Vendor',

View File

@ -0,0 +1,113 @@
import ProductCategoryIcon from '../../components/Icons/ProductCategoryIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const ProductCategory = {
name: 'productCategory',
label: 'Product Category',
prefix: 'PCG',
endpoint: 'productcategories',
icon: ProductCategoryIcon,
actions: [
{
name: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon,
url: (_id) =>
`/dashboard/management/productcategories/info?productCategoryId=${_id}`
},
{
name: 'edit',
label: 'Edit',
row: true,
icon: EditIcon,
url: (_id) =>
`/dashboard/management/productcategories/info?productCategoryId=${_id}&action=edit`,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
}
},
{
name: 'finishEdit',
label: 'Save Edits',
icon: CheckIcon,
url: (_id) =>
`/dashboard/management/productcategories/info?productCategoryId=${_id}&action=finishEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
icon: XMarkIcon,
url: (_id) =>
`/dashboard/management/productcategories/info?productCategoryId=${_id}&action=cancelEdit`,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{ type: 'divider' },
{
name: 'delete',
label: 'Delete',
icon: BinIcon,
danger: true,
url: (_id) =>
`/dashboard/management/productcategories/info?productCategoryId=${_id}&action=delete`
}
],
url: (id) =>
`/dashboard/management/productcategories/info?productCategoryId=${id}`,
columns: ['_reference', 'name', 'createdAt', 'updatedAt'],
filters: ['_id', 'name'],
sorters: ['name', 'createdAt', 'updatedAt', '_id'],
properties: [
{
name: '_id',
label: 'ID',
columnFixed: 'left',
type: 'id',
objectType: 'productCategory',
showCopy: true,
readOnly: true,
columnWidth: 140
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
objectType: 'productCategory',
showCopy: true,
readOnly: true
},
{
name: 'name',
label: 'Name',
columnFixed: 'left',
required: true,
type: 'text',
columnWidth: 200
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
}
]
}

View File

@ -4,7 +4,6 @@ import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
export const ProductSku = {
name: 'productSku',
@ -20,13 +19,6 @@ export const ProductSku = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/productskus/info?productSkuId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/productskus/info?productSkuId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

View File

@ -172,8 +172,26 @@ export const PurchaseOrder = {
}
],
group: ['vendor'],
filters: ['vendor'],
sorters: ['createdAt', 'state', 'updatedAt'],
filters: [
'vendor',
'totalAmount',
'totalAmountWithTax',
'totalTaxAmount',
'shippingAmount',
'shippingAmountWithTax',
'grandTotalAmount'
],
sorters: [
'createdAt',
'state',
'updatedAt',
'totalAmount',
'totalAmountWithTax',
'totalTaxAmount',
'shippingAmount',
'shippingAmountWithTax',
'grandTotalAmount'
],
columns: [
'_reference',
'state',
@ -260,7 +278,7 @@ export const PurchaseOrder = {
prefix: '£',
roundNumber: 2,
readOnly: true,
columnWidth: 175
columnWidth: 185
},
{
name: 'completedAt',
@ -275,7 +293,7 @@ export const PurchaseOrder = {
type: 'number',
prefix: '£',
readOnly: true,
columnWidth: 175,
columnWidth: 215,
roundNumber: 2
},
{
@ -285,7 +303,7 @@ export const PurchaseOrder = {
prefix: '£',
roundNumber: 2,
readOnly: true,
columnWidth: 150
columnWidth: 190
},
{
name: 'shippingAmountWithTax',
@ -294,7 +312,7 @@ export const PurchaseOrder = {
prefix: '£',
readOnly: true,
roundNumber: 2,
columnWidth: 200
columnWidth: 240
},
{
name: 'totalAmount',
@ -303,7 +321,7 @@ export const PurchaseOrder = {
prefix: '£',
roundNumber: 2,
readOnly: true,
columnWidth: 150
columnWidth: 170
},
{
name: 'grandTotalAmount',
@ -311,7 +329,7 @@ export const PurchaseOrder = {
type: 'number',
prefix: '£',
roundNumber: 2,
columnWidth: 175,
columnWidth: 215,
readOnly: true
}
],

View File

@ -170,7 +170,7 @@ export const SalesOrder = {
}
}
],
group: ['client', 'marketplace'],
group: ['client'],
filters: ['client', 'marketplace'],
sorters: ['createdAt', 'state', 'updatedAt'],
columns: [

View File

@ -85,9 +85,9 @@ export const StockTransfer = {
}
],
url: (id) => `/dashboard/inventory/stocktransfers/info?stockTransferId=${id}`,
filters: ['_id', 'state'],
sorters: ['createdAt', 'postedAt'],
columns: ['_reference', 'state', 'postedAt', 'createdAt', 'updatedAt'],
filters: ['_id', 'name', 'state'],
sorters: ['name', 'createdAt', 'postedAt'],
columns: ['_reference', 'name', 'state', 'postedAt', 'createdAt', 'updatedAt'],
properties: [
{
name: '_id',
@ -114,6 +114,14 @@ export const StockTransfer = {
showCopy: true,
readOnly: true
},
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
columnWidth: 220,
columnFixed: 'left'
},
{
name: 'state',
label: 'State',
@ -177,6 +185,38 @@ export const StockTransfer = {
suffix: (row) =>
row?.fromStockType === 'filamentStock' ? 'g net' : null
},
{
name: 'available',
label: 'Available',
type: 'number',
readOnly: true,
columnWidth: 140,
value: (row) => {
if (row?.fromStockType === 'filamentStock') {
return row?.fromStock?.currentWeight?.net ?? 0
} else {
return row?.fromStock?.currentQuantity ?? 0
}
},
suffix: (row) =>
row?.fromStockType === 'filamentStock' ? 'g net' : null
},
{
name: 'remaining',
label: 'Remaining',
type: 'number',
readOnly: true,
columnWidth: 140,
value: (row) => {
const quantity = row?.quantity ?? 0
if (row?.fromStockType === 'filamentStock') {
return (row?.fromStock?.currentWeight?.net ?? 0) - quantity
}
return (row?.fromStock?.currentQuantity ?? 0) - quantity
},
suffix: (row) =>
row?.fromStockType === 'filamentStock' ? 'g net' : null
},
{
name: 'toStockLocation',
label: 'To location',

View File

@ -3,7 +3,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import BinIcon from '../../components/Icons/BinIcon'
export const TaxRate = {
@ -20,13 +19,6 @@ export const TaxRate = {
icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/taxrates/info?taxRateId=${_id}`
},
{
name: 'reload',
label: 'Reload',
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/taxrates/info?taxRateId=${_id}&action=reload`
},
{
name: 'edit',
label: 'Edit',

Some files were not shown because too many files have changed in this diff Show More