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.
This commit is contained in:
parent
7a5ea5416b
commit
fb9454d8e0
16
assets/icons/productcategoryicon.svg
Normal file
16
assets/icons/productcategoryicon.svg
Normal 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 |
@ -90,7 +90,7 @@ const StockTransfers = () => {
|
||||
open={newOpen}
|
||||
styles={{ content: { paddingBottom: '24px' } }}
|
||||
footer={null}
|
||||
width={960}
|
||||
width={740}
|
||||
onCancel={() => {
|
||||
setNewOpen(false)
|
||||
}}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -68,6 +68,10 @@ const HostInfo = () => {
|
||||
finishEdit: () => {
|
||||
objectFormRef?.current.handleUpdate()
|
||||
return true
|
||||
},
|
||||
delete: () => {
|
||||
objectFormRef?.current?.handleDelete?.()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
109
src/components/Dashboard/Management/ProductCategories.jsx
Normal file
109
src/components/Dashboard/Management/ProductCategories.jsx
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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,6 +47,7 @@ const UserInfo = () => {
|
||||
loading: false,
|
||||
objectData: {}
|
||||
})
|
||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||
|
||||
const actions = {
|
||||
newAppPassword: () => {
|
||||
@ -147,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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -12,6 +12,7 @@ const breadcrumbNameMap = {
|
||||
developer: 'Developer',
|
||||
finance: 'Finance',
|
||||
sales: 'Sales',
|
||||
productcategories: 'Product Categories',
|
||||
overview: 'Overview',
|
||||
info: 'Info',
|
||||
design: 'Design',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import ObjectSelect from './ObjectSelect'
|
||||
import FileList from './FileList'
|
||||
import PlusIcon from '../../Icons/PlusIcon'
|
||||
@ -51,7 +58,9 @@ const FileUpload = ({
|
||||
// Update currentFiles when value prop changes
|
||||
useEffect(() => {
|
||||
setCurrentFiles((prev) => {
|
||||
if (getFileIdentity(prev, multiple) === getFileIdentity(value, multiple)) {
|
||||
if (
|
||||
getFileIdentity(prev, multiple) === getFileIdentity(value, multiple)
|
||||
) {
|
||||
return prev
|
||||
}
|
||||
|
||||
@ -128,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'}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -446,6 +446,14 @@ const ObjectTable = forwardRef(
|
||||
[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(() => {
|
||||
if (pages.length > 0 && connected == true) {
|
||||
@ -517,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) => {
|
||||
@ -663,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
|
||||
})
|
||||
}
|
||||
@ -858,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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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: {
|
||||
|
||||
8
src/components/Icons/ProductCategoryIcon.jsx
Normal file
8
src/components/Icons/ProductCategoryIcon.jsx
Normal 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
|
||||
@ -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,
|
||||
|
||||
@ -70,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'],
|
||||
@ -121,7 +129,7 @@ export const File = {
|
||||
type: 'text',
|
||||
readOnly: true,
|
||||
required: true,
|
||||
columnWidth: 120
|
||||
columnWidth: 190
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
|
||||
@ -82,8 +82,23 @@ export const GCodeFile = {
|
||||
'gcodeFileInfo.hotPlateTemp',
|
||||
'updatedAt'
|
||||
],
|
||||
filters: ['_id', 'name', 'filament', 'filament._id', 'filamentSku', 'cost', 'updatedAt'],
|
||||
sorters: ['name', 'filament', 'filamentSku', 'cost', 'createdAt', 'updatedAt'],
|
||||
filters: [
|
||||
'_id',
|
||||
'name',
|
||||
'filament',
|
||||
'filament._id',
|
||||
'filamentSku',
|
||||
'cost',
|
||||
'updatedAt'
|
||||
],
|
||||
sorters: [
|
||||
'name',
|
||||
'filament',
|
||||
'filamentSku',
|
||||
'cost',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
],
|
||||
group: ['filament', 'filamentSku'],
|
||||
properties: [
|
||||
{
|
||||
@ -255,6 +270,7 @@ export const GCodeFile = {
|
||||
label: 'Parts',
|
||||
type: 'objectChildren',
|
||||
objectType: 'part',
|
||||
size: 'medium',
|
||||
properties: [
|
||||
{
|
||||
name: 'part',
|
||||
|
||||
@ -3,6 +3,7 @@ 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'
|
||||
import OTPIcon from '../../components/Icons/OTPIcon'
|
||||
|
||||
export const Host = {
|
||||
@ -60,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'],
|
||||
|
||||
@ -68,6 +68,7 @@ export const Product = {
|
||||
columns: [
|
||||
'_reference',
|
||||
'name',
|
||||
'productCategory',
|
||||
'tags',
|
||||
'vendor',
|
||||
'cost',
|
||||
@ -77,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',
|
||||
@ -120,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',
|
||||
|
||||
113
src/database/models/ProductCategory.js
Normal file
113
src/database/models/ProductCategory.js
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
],
|
||||
|
||||
@ -170,7 +170,7 @@ export const SalesOrder = {
|
||||
}
|
||||
}
|
||||
],
|
||||
group: ['client', 'marketplace'],
|
||||
group: ['client'],
|
||||
filters: ['client', 'marketplace'],
|
||||
sorters: ['createdAt', 'state', 'updatedAt'],
|
||||
columns: [
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -11,6 +11,8 @@ const PartSkus = lazy(() => import('../components/Dashboard/Management/PartSkus.
|
||||
const PartSkuInfo = lazy(() => import('../components/Dashboard/Management/PartSkus/PartSkuInfo.jsx'))
|
||||
const Products = lazy(() => import('../components/Dashboard/Management/Products.jsx'))
|
||||
const ProductInfo = lazy(() => import('../components/Dashboard/Management/Products/ProductInfo.jsx'))
|
||||
const ProductCategories = lazy(() => import('../components/Dashboard/Management/ProductCategories.jsx'))
|
||||
const ProductCategoryInfo = lazy(() => import('../components/Dashboard/Management/ProductCategories/ProductCategoryInfo.jsx'))
|
||||
const ProductSkus = lazy(() => import('../components/Dashboard/Management/ProductSkus.jsx'))
|
||||
const ProductSkuInfo = lazy(() => import('../components/Dashboard/Management/ProductSkus/ProductSkuInfo.jsx'))
|
||||
const Vendors = lazy(() => import('../components/Dashboard/Management/Vendors'))
|
||||
@ -79,6 +81,16 @@ const ManagementRoutes = [
|
||||
path='management/products/info'
|
||||
element={<ProductInfo />}
|
||||
/>,
|
||||
<Route
|
||||
key='productcategories'
|
||||
path='management/productcategories'
|
||||
element={<ProductCategories />}
|
||||
/>,
|
||||
<Route
|
||||
key='productcategories-info'
|
||||
path='management/productcategories/info'
|
||||
element={<ProductCategoryInfo />}
|
||||
/>,
|
||||
<Route key='productskus' path='management/productskus' element={<ProductSkus />} />,
|
||||
<Route
|
||||
key='productskus-info'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user