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}
|
open={newOpen}
|
||||||
styles={{ content: { paddingBottom: '24px' } }}
|
styles={{ content: { paddingBottom: '24px' } }}
|
||||||
footer={null}
|
footer={null}
|
||||||
width={960}
|
width={740}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setNewOpen(false)
|
setNewOpen(false)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,17 +1,40 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import NewObjectForm from '../../common/NewObjectForm'
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
import WizardView from '../../common/WizardView'
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
|
const defaultTransferName = () =>
|
||||||
|
`Transfer ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`
|
||||||
|
|
||||||
const NewStockTransfer = ({ onOk, reset }) => {
|
const NewStockTransfer = ({ onOk, reset }) => {
|
||||||
return (
|
return (
|
||||||
<NewObjectForm
|
<NewObjectForm
|
||||||
type={'stockTransfer'}
|
type={'stockTransfer'}
|
||||||
reset={reset}
|
reset={reset}
|
||||||
defaultValues={{ state: { type: 'draft' }, lines: [] }}
|
defaultValues={{
|
||||||
|
name: defaultTransferName(),
|
||||||
|
state: { type: 'draft' },
|
||||||
|
lines: []
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{({ handleSubmit, submitLoading, objectData }) => {
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
const steps = [
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='stockTransfer'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
labelWidth={80}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Summary',
|
title: 'Summary',
|
||||||
key: 'summary',
|
key: 'summary',
|
||||||
@ -20,6 +43,7 @@ const NewStockTransfer = ({ onOk, reset }) => {
|
|||||||
type='stockTransfer'
|
type='stockTransfer'
|
||||||
column={1}
|
column={1}
|
||||||
bordered={false}
|
bordered={false}
|
||||||
|
labelWidth={80}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
_id: false,
|
_id: false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
@ -38,7 +62,7 @@ const NewStockTransfer = ({ onOk, reset }) => {
|
|||||||
<WizardView
|
<WizardView
|
||||||
steps={steps}
|
steps={steps}
|
||||||
loading={submitLoading}
|
loading={submitLoading}
|
||||||
formValid={true}
|
formValid={formValid}
|
||||||
title='New Stock Transfer'
|
title='New Stock Transfer'
|
||||||
onSubmit={async () => {
|
onSubmit={async () => {
|
||||||
const result = await handleSubmit()
|
const result = await handleSubmit()
|
||||||
|
|||||||
@ -68,6 +68,10 @@ const HostInfo = () => {
|
|||||||
finishEdit: () => {
|
finishEdit: () => {
|
||||||
objectFormRef?.current.handleUpdate()
|
objectFormRef?.current.handleUpdate()
|
||||||
return true
|
return true
|
||||||
|
},
|
||||||
|
delete: () => {
|
||||||
|
objectFormRef?.current?.handleDelete?.()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import FilamentSkuIcon from '../../Icons/FilamentSkuIcon'
|
|||||||
import PartIcon from '../../Icons/PartIcon'
|
import PartIcon from '../../Icons/PartIcon'
|
||||||
import PartSkuIcon from '../../Icons/PartSkuIcon'
|
import PartSkuIcon from '../../Icons/PartSkuIcon'
|
||||||
import ProductIcon from '../../Icons/ProductIcon'
|
import ProductIcon from '../../Icons/ProductIcon'
|
||||||
|
import ProductCategoryIcon from '../../Icons/ProductCategoryIcon'
|
||||||
import ProductSkuIcon from '../../Icons/ProductSkuIcon'
|
import ProductSkuIcon from '../../Icons/ProductSkuIcon'
|
||||||
import VendorIcon from '../../Icons/VendorIcon'
|
import VendorIcon from '../../Icons/VendorIcon'
|
||||||
import MaterialIcon from '../../Icons/MaterialIcon'
|
import MaterialIcon from '../../Icons/MaterialIcon'
|
||||||
@ -57,6 +58,12 @@ const items = [
|
|||||||
label: 'Products',
|
label: 'Products',
|
||||||
path: '/dashboard/management/products'
|
path: '/dashboard/management/products'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'productCategories',
|
||||||
|
icon: <ProductCategoryIcon />,
|
||||||
|
label: 'Product Categories',
|
||||||
|
path: '/dashboard/management/productcategories'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'productSkus',
|
key: 'productSkus',
|
||||||
icon: <ProductSkuIcon />,
|
icon: <ProductSkuIcon />,
|
||||||
@ -199,6 +206,7 @@ const routeKeyMap = {
|
|||||||
'/dashboard/management/users': 'users',
|
'/dashboard/management/users': 'users',
|
||||||
'/dashboard/management/apppasswords': 'appPasswords',
|
'/dashboard/management/apppasswords': 'appPasswords',
|
||||||
'/dashboard/management/products': 'products',
|
'/dashboard/management/products': 'products',
|
||||||
|
'/dashboard/management/productcategories': 'productCategories',
|
||||||
'/dashboard/management/productskus': 'productSkus',
|
'/dashboard/management/productskus': 'productSkus',
|
||||||
'/dashboard/management/vendors': 'vendors',
|
'/dashboard/management/vendors': 'vendors',
|
||||||
'/dashboard/management/couriers': 'couriers',
|
'/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 UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import NewAppPassword from '../AppPasswords/NewAppPassword.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 UserInfo = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
@ -44,6 +47,7 @@ const UserInfo = () => {
|
|||||||
loading: false,
|
loading: false,
|
||||||
objectData: {}
|
objectData: {}
|
||||||
})
|
})
|
||||||
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
newAppPassword: () => {
|
newAppPassword: () => {
|
||||||
@ -147,13 +151,34 @@ const UserInfo = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ loading, isEditing, objectData }) => (
|
{({ loading, isEditing, objectData }) => (
|
||||||
<ObjectInfo
|
<Flex gap='large' vertical={isMobile}>
|
||||||
loading={loading}
|
<div
|
||||||
indicator={<LoadingOutlined />}
|
style={
|
||||||
isEditing={isEditing}
|
isMobile
|
||||||
type='user'
|
? { width: '100%' }
|
||||||
objectData={objectData}
|
: { 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>
|
</ObjectForm>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const NewGCodeFile = ({ onOk, defaultValues }) => {
|
|||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: 'Upload',
|
title: 'Upload',
|
||||||
key: 'uplaod',
|
key: 'upload',
|
||||||
content: (
|
content: (
|
||||||
<ObjectInfo
|
<ObjectInfo
|
||||||
type='gcodeFile'
|
type='gcodeFile'
|
||||||
@ -27,7 +27,7 @@ const NewGCodeFile = ({ onOk, defaultValues }) => {
|
|||||||
isEditing={true}
|
isEditing={true}
|
||||||
required={true}
|
required={true}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
visibleProperties={{ name: false, filament: false }}
|
visibleProperties={{ file: true }}
|
||||||
showLabels={false}
|
showLabels={false}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -57,6 +57,8 @@ const NewGCodeFile = ({ onOk, defaultValues }) => {
|
|||||||
bordered={false}
|
bordered={false}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
_id: false,
|
_id: false,
|
||||||
|
_reference: false,
|
||||||
|
parts: false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
startedAt: false
|
startedAt: false
|
||||||
|
|||||||
@ -12,6 +12,7 @@ const breadcrumbNameMap = {
|
|||||||
developer: 'Developer',
|
developer: 'Developer',
|
||||||
finance: 'Finance',
|
finance: 'Finance',
|
||||||
sales: 'Sales',
|
sales: 'Sales',
|
||||||
|
productcategories: 'Product Categories',
|
||||||
overview: 'Overview',
|
overview: 'Overview',
|
||||||
info: 'Info',
|
info: 'Info',
|
||||||
design: 'Design',
|
design: 'Design',
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const FileList = ({
|
|||||||
showInfo = true,
|
showInfo = true,
|
||||||
showDownload = true,
|
showDownload = true,
|
||||||
defaultPreviewOpen = false,
|
defaultPreviewOpen = false,
|
||||||
|
minimal = false,
|
||||||
card = true
|
card = true
|
||||||
}) => {
|
}) => {
|
||||||
const { fetchFileContent, flushFile } = useContext(ApiServerContext)
|
const { fetchFileContent, flushFile } = useContext(ApiServerContext)
|
||||||
@ -78,7 +79,7 @@ const FileList = ({
|
|||||||
<Tag>{file.extension}</Tag>
|
<Tag>{file.extension}</Tag>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={'small'} align='center'>
|
<Flex gap={'small'} align='center'>
|
||||||
{showDownload && (
|
{showDownload && !minimal && (
|
||||||
<Button
|
<Button
|
||||||
icon={<DownloadIcon />}
|
icon={<DownloadIcon />}
|
||||||
size='small'
|
size='small'
|
||||||
@ -86,7 +87,7 @@ const FileList = ({
|
|||||||
onClick={() => handleDownload(file)}
|
onClick={() => handleDownload(file)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showPreview && (
|
{showPreview && !minimal && (
|
||||||
<Button
|
<Button
|
||||||
icon={previewOpen ? <EyeSlashIcon /> : <EyeIcon />}
|
icon={previewOpen ? <EyeSlashIcon /> : <EyeIcon />}
|
||||||
size='small'
|
size='small'
|
||||||
@ -100,7 +101,7 @@ const FileList = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showInfo && (
|
{showInfo && !minimal && (
|
||||||
<Button
|
<Button
|
||||||
icon={<InfoCircleIcon />}
|
icon={<InfoCircleIcon />}
|
||||||
size='small'
|
size='small'
|
||||||
@ -110,7 +111,7 @@ const FileList = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{editing && (
|
{editing && !minimal && (
|
||||||
<Button
|
<Button
|
||||||
icon={<BinIcon />}
|
icon={<BinIcon />}
|
||||||
size='small'
|
size='small'
|
||||||
@ -120,7 +121,7 @@ const FileList = ({
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
{previewOpen ? (
|
{previewOpen && !minimal ? (
|
||||||
<>
|
<>
|
||||||
<Divider style={{ marginTop: 0, marginBottom: card ? 0 : '4px' }} />
|
<Divider style={{ marginTop: 0, marginBottom: card ? 0 : '4px' }} />
|
||||||
<FilePreview file={file} style={{ width: '100%' }} />
|
<FilePreview file={file} style={{ width: '100%' }} />
|
||||||
@ -174,7 +175,8 @@ FileList.propTypes = {
|
|||||||
showInfo: PropTypes.bool,
|
showInfo: PropTypes.bool,
|
||||||
showDownload: PropTypes.bool,
|
showDownload: PropTypes.bool,
|
||||||
defaultPreviewOpen: PropTypes.bool,
|
defaultPreviewOpen: PropTypes.bool,
|
||||||
card: PropTypes.bool
|
card: PropTypes.bool,
|
||||||
|
minimal: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileList
|
export default FileList
|
||||||
|
|||||||
@ -2,7 +2,14 @@ import { Upload, Button, Flex, Typography, Space, Progress, Card } from 'antd'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import UploadIcon from '../../Icons/UploadIcon'
|
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 ObjectSelect from './ObjectSelect'
|
||||||
import FileList from './FileList'
|
import FileList from './FileList'
|
||||||
import PlusIcon from '../../Icons/PlusIcon'
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
@ -51,7 +58,9 @@ const FileUpload = ({
|
|||||||
// Update currentFiles when value prop changes
|
// Update currentFiles when value prop changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentFiles((prev) => {
|
setCurrentFiles((prev) => {
|
||||||
if (getFileIdentity(prev, multiple) === getFileIdentity(value, multiple)) {
|
if (
|
||||||
|
getFileIdentity(prev, multiple) === getFileIdentity(value, multiple)
|
||||||
|
) {
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +137,7 @@ const FileUpload = ({
|
|||||||
return (
|
return (
|
||||||
<Flex gap={'small'} vertical>
|
<Flex gap={'small'} vertical>
|
||||||
{hasNoItems && uploading == false ? (
|
{hasNoItems && uploading == false ? (
|
||||||
<Flex gap={'small'} align='center'>
|
<Flex gap={'small'} align='center' wrap>
|
||||||
<Space.Compact style={{ flexGrow: 1 }}>
|
<Space.Compact style={{ flexGrow: 1 }}>
|
||||||
<ObjectSelect
|
<ObjectSelect
|
||||||
type={'file'}
|
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 { Form } from 'antd'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import { useMessageContext } from '../context/MessageContext'
|
import { useMessageContext } from '../context/MessageContext'
|
||||||
@ -38,10 +38,35 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
const [submitLoading, setSubmitLoading] = useState(false)
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
const [formValid, setFormValid] = useState(false)
|
const [formValid, setFormValid] = useState(false)
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
|
const validationRunRef = useRef(0)
|
||||||
const formUpdateValues = Form.useWatch([], form)
|
const formUpdateValues = Form.useWatch([], form)
|
||||||
const { showSuccess, showError: showMessageError } = useMessageContext()
|
const { showSuccess, showError: showMessageError } = useMessageContext()
|
||||||
const { createObject, showError } = useContext(ApiServerContext)
|
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
|
// Get the model definition for this object type
|
||||||
const model = getModelByName(type)
|
const model = getModelByName(type)
|
||||||
|
|
||||||
@ -141,21 +166,15 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
const computedValuesObject = buildObjectFromEntries(computedEntries)
|
const computedValuesObject = buildObjectFromEntries(computedEntries)
|
||||||
const initialFormData = merge({}, defaultValues, computedValuesObject)
|
const initialFormData = merge({}, defaultValues, computedValuesObject)
|
||||||
form.setFieldsValue(initialFormData)
|
form.setFieldsValue(initialFormData)
|
||||||
form
|
|
||||||
.validateFields({ validateOnly: true })
|
|
||||||
.then(() => setFormValid(true))
|
|
||||||
.catch(() => setFormValid(false))
|
|
||||||
setObjectData((prev) => merge({}, prev, initialFormData))
|
setObjectData((prev) => merge({}, prev, initialFormData))
|
||||||
|
return validateForm()
|
||||||
}
|
}
|
||||||
}, [form, defaultValues, calculateComputedValues, model])
|
}, [form, defaultValues, calculateComputedValues, model, validateForm])
|
||||||
|
|
||||||
// Validate form on change
|
// Validate form on change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form
|
return validateForm()
|
||||||
.validateFields({ validateOnly: true })
|
}, [validateForm, formUpdateValues])
|
||||||
.then(() => setFormValid(true))
|
|
||||||
.catch(() => setFormValid(false))
|
|
||||||
}, [form, formUpdateValues])
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
@ -217,7 +236,7 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children({
|
{children({
|
||||||
submitLoading: submitLoading,
|
submitLoading,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
form,
|
form,
|
||||||
formValid,
|
formValid,
|
||||||
|
|||||||
@ -103,11 +103,10 @@ const NoteItem = ({
|
|||||||
if (isExpanded == true) {
|
if (isExpanded == true) {
|
||||||
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
|
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
|
||||||
'note',
|
'note',
|
||||||
(noteData) => {
|
{ 'parent._id': note._id },
|
||||||
if (noteData.parent._id == note._id) {
|
() => {
|
||||||
if (isExpanded == true) {
|
if (isExpanded == true) {
|
||||||
handleNoteExpand()
|
handleNoteExpand()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -95,11 +95,8 @@ const NotesPanel = ({ _id, type }) => {
|
|||||||
if (connected == true && subscribeToObjectTypeUpdatesRef.current == null) {
|
if (connected == true && subscribeToObjectTypeUpdatesRef.current == null) {
|
||||||
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
|
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
|
||||||
'note',
|
'note',
|
||||||
(noteData) => {
|
{ 'parent._id': _id },
|
||||||
if (noteData.parent._id == _id) {
|
() => handleReloadData()
|
||||||
handleReloadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@ -99,6 +99,14 @@ const ObjectProperty = ({
|
|||||||
value = value(objectData)
|
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) {
|
if (objectType && typeof objectType == 'function' && objectData) {
|
||||||
objectType = objectType(objectData)
|
objectType = objectType(objectData)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -446,6 +446,14 @@ const ObjectTable = forwardRef(
|
|||||||
[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
|
// Subscribe to real-time updates for all items
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pages.length > 0 && connected == true) {
|
if (pages.length > 0 && connected == true) {
|
||||||
@ -517,16 +525,28 @@ const ObjectTable = forwardRef(
|
|||||||
}, [connected])
|
}, [connected])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (connected == true) {
|
||||||
connected == true &&
|
const unsubscribe = subscribeToObjectTypeUpdates(
|
||||||
subscribeToObjectTypeUpdatesRef.current == null
|
|
||||||
) {
|
|
||||||
subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
|
|
||||||
type,
|
type,
|
||||||
|
subscriptionFilter,
|
||||||
newEventHandler
|
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(
|
const updateData = useCallback(
|
||||||
(id, updatedData) => {
|
(id, updatedData) => {
|
||||||
@ -663,11 +683,13 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log('filters--', filters)
|
||||||
|
|
||||||
setSidebarFilter(next)
|
setSidebarFilter(next)
|
||||||
setPages([])
|
setPages([])
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
loadPage(initialPage, getActiveFilter(next), {
|
loadPage(initialPage, getActiveFilter(next), {
|
||||||
field: sorter.field,
|
field: sorter.columnKey,
|
||||||
order: sorter.order
|
order: sorter.order
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -858,29 +880,42 @@ const ObjectTable = forwardRef(
|
|||||||
style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
|
style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
|
||||||
ref={cardsContainerRef}
|
ref={cardsContainerRef}
|
||||||
>
|
>
|
||||||
{tableData.map((record) => (
|
{tableData.map((record) => {
|
||||||
<Col xs={24} sm={12} md={12} lg={8} xl={6} xxl={6} key={record._id}>
|
if (record?._id == undefined) {
|
||||||
<div style={{ width: '100%', overflow: 'hidden' }}>
|
return null
|
||||||
<RowForm
|
}
|
||||||
record={record}
|
return (
|
||||||
isEditing={isEditing}
|
<Col
|
||||||
onRegister={registerForm}
|
xs={24}
|
||||||
>
|
sm={12}
|
||||||
<Flex align={'center'} vertical gap={'middle'}>
|
md={12}
|
||||||
<ObjectCard
|
lg={8}
|
||||||
model={model}
|
xl={6}
|
||||||
modelProperties={modelProperties}
|
xxl={6}
|
||||||
visibleColumns={visibleColumns}
|
key={record._id}
|
||||||
record={record}
|
>
|
||||||
isEditing={isEditing}
|
<div style={{ width: '100%', overflow: 'hidden' }}>
|
||||||
rowActions={rowActions}
|
<RowForm
|
||||||
renderActions={renderActions}
|
record={record}
|
||||||
/>
|
isEditing={isEditing}
|
||||||
</Flex>
|
onRegister={registerForm}
|
||||||
</RowForm>
|
>
|
||||||
</div>
|
<Flex align={'center'} vertical gap={'middle'}>
|
||||||
</Col>
|
<ObjectCard
|
||||||
))}
|
model={model}
|
||||||
|
modelProperties={modelProperties}
|
||||||
|
visibleColumns={visibleColumns}
|
||||||
|
record={record}
|
||||||
|
isEditing={isEditing}
|
||||||
|
rowActions={rowActions}
|
||||||
|
renderActions={renderActions}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</RowForm>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -136,6 +136,10 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
|
|||||||
status = 'cyan'
|
status = 'cyan'
|
||||||
text = 'Ordered'
|
text = 'Ordered'
|
||||||
break
|
break
|
||||||
|
case 'posted':
|
||||||
|
status = 'magenta'
|
||||||
|
text = 'Posted'
|
||||||
|
break
|
||||||
case 'received':
|
case 'received':
|
||||||
status = 'success'
|
status = 'success'
|
||||||
text = 'Received'
|
text = 'Received'
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
|
|||||||
import ReloadIcon from '../../Icons/ReloadIcon'
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
import config from '../../../config'
|
import config from '../../../config'
|
||||||
import loglevel from 'loglevel'
|
import loglevel from 'loglevel'
|
||||||
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
const logger = loglevel.getLogger('ApiServerContext')
|
const logger = loglevel.getLogger('ApiServerContext')
|
||||||
logger.setLevel(config.logLevel)
|
logger.setLevel(config.logLevel)
|
||||||
|
|
||||||
@ -25,6 +26,35 @@ const SPOTLIGHT_CACHE_TTL_MS = 10_000
|
|||||||
const spotlightCache = new Map()
|
const spotlightCache = new Map()
|
||||||
const runningSpotlightFetches = 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 ApiServerContext = createContext()
|
||||||
|
|
||||||
const ApiServerProvider = ({ children }) => {
|
const ApiServerProvider = ({ children }) => {
|
||||||
@ -316,12 +346,16 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
const handleObjectNew = async (data) => {
|
const handleObjectNew = async (data) => {
|
||||||
logger.debug('Notifying object new:', data)
|
logger.debug('Notifying object new:', data)
|
||||||
const objectType = data.objectType || 'unknown'
|
const objectType = data.objectType || 'unknown'
|
||||||
|
const callbacksRefKey = getObjectTypeSubscriptionKey(
|
||||||
|
objectType,
|
||||||
|
data.filter || {}
|
||||||
|
)
|
||||||
|
|
||||||
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
|
if (objectType && subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
||||||
const callbacks = subscribedCallbacksRef.current.get(objectType)
|
const callbacks = subscribedCallbacksRef.current.get(callbacksRefKey)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Calling ${callbacks.length} callbacks for type:`,
|
`Calling ${callbacks.length} callbacks for type:`,
|
||||||
objectType
|
callbacksRefKey
|
||||||
)
|
)
|
||||||
callbacks.forEach((callback) => {
|
callbacks.forEach((callback) => {
|
||||||
try {
|
try {
|
||||||
@ -332,7 +366,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
|
`No callbacks found for object: ${callbacksRefKey}, subscribed callbacks:`,
|
||||||
Array.from(subscribedCallbacksRef.current.keys())
|
Array.from(subscribedCallbacksRef.current.keys())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -341,12 +375,16 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
const handleObjectDelete = async (data) => {
|
const handleObjectDelete = async (data) => {
|
||||||
logger.debug('Notifying object delete:', data)
|
logger.debug('Notifying object delete:', data)
|
||||||
const objectType = data.objectType || 'unknown'
|
const objectType = data.objectType || 'unknown'
|
||||||
|
const callbacksRefKey = getObjectTypeSubscriptionKey(
|
||||||
|
objectType,
|
||||||
|
data.filter || {}
|
||||||
|
)
|
||||||
|
|
||||||
if (objectType && subscribedCallbacksRef.current.has(objectType)) {
|
if (objectType && subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
||||||
const callbacks = subscribedCallbacksRef.current.get(objectType)
|
const callbacks = subscribedCallbacksRef.current.get(callbacksRefKey)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Calling ${callbacks.length} callbacks for type:`,
|
`Calling ${callbacks.length} callbacks for type:`,
|
||||||
objectType
|
callbacksRefKey
|
||||||
)
|
)
|
||||||
callbacks.forEach((callback) => {
|
callbacks.forEach((callback) => {
|
||||||
try {
|
try {
|
||||||
@ -357,7 +395,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`No callbacks found for object: ${objectType}, subscribed callbacks:`,
|
`No callbacks found for object: ${callbacksRefKey}, subscribed callbacks:`,
|
||||||
Array.from(subscribedCallbacksRef.current.keys())
|
Array.from(subscribedCallbacksRef.current.keys())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -389,24 +427,29 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const offObjectTypeUpdatesEvent = useCallback((objectType, callback) => {
|
const offObjectTypeUpdatesEvent = useCallback(
|
||||||
if (socketRef.current && socketRef.current.connected == true) {
|
(objectType, filter, callback) => {
|
||||||
// Remove callback from the subscribed callbacks map
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
if (subscribedCallbacksRef.current.has(objectType)) {
|
const callbacksRefKey = getObjectTypeSubscriptionKey(objectType, filter)
|
||||||
const callbacks = subscribedCallbacksRef.current
|
// Remove callback from the subscribed callbacks map
|
||||||
.get(objectType)
|
if (subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
||||||
.filter((cb) => cb !== callback)
|
const callbacks = subscribedCallbacksRef.current
|
||||||
if (callbacks.length === 0) {
|
.get(callbacksRefKey)
|
||||||
subscribedCallbacksRef.current.delete(objectType)
|
.filter((cb) => cb !== callback)
|
||||||
socketRef.current.emit('unsubscribeObjectTypeUpdate', {
|
if (callbacks.length === 0) {
|
||||||
objectType: objectType
|
subscribedCallbacksRef.current.delete(callbacksRefKey)
|
||||||
})
|
socketRef.current.emit('unsubscribeObjectTypeUpdate', {
|
||||||
} else {
|
objectType: objectType,
|
||||||
subscribedCallbacksRef.current.set(objectType, callbacks)
|
filter: filter
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, [])
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
const subscribeToObjectUpdates = useCallback(
|
const subscribeToObjectUpdates = useCallback(
|
||||||
(id, objectType, callback) => {
|
(id, objectType, callback) => {
|
||||||
@ -471,31 +514,46 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}, [connected, userProfile?._id, subscribeToObjectUpdates, setUserProfile])
|
}, [connected, userProfile?._id, subscribeToObjectUpdates, setUserProfile])
|
||||||
|
|
||||||
const subscribeToObjectTypeUpdates = useCallback(
|
const subscribeToObjectTypeUpdates = useCallback(
|
||||||
(objectType, callback) => {
|
(objectType, filterOrCallback = {}, maybeCallback) => {
|
||||||
logger.debug('Subscribing to type updates:', objectType)
|
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) {
|
if (socketRef.current && socketRef.current.connected == true) {
|
||||||
// Add callback to the subscribed callbacks map immediately
|
// Add callback to the subscribed callbacks map immediately
|
||||||
if (!subscribedCallbacksRef.current.has(objectType)) {
|
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
||||||
subscribedCallbacksRef.current.set(objectType, [])
|
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(
|
const callbacksLength =
|
||||||
'subscribeToObjectTypeUpdate',
|
subscribedCallbacksRef.current.get(callbacksRefKey).length
|
||||||
{ objectType: objectType },
|
|
||||||
(result) => {
|
if (callbacksLength <= 0) {
|
||||||
if (result.success) {
|
socketRef.current.emit(
|
||||||
logger.info('Subscribed to objectType:', objectType)
|
'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 cleanup function
|
||||||
return () => offObjectTypeUpdatesEvent(objectType, callback)
|
return () => offObjectTypeUpdatesEvent(objectType, filter, callback)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[offObjectTypeUpdatesEvent]
|
[offObjectTypeUpdatesEvent]
|
||||||
@ -708,7 +766,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Generalized fetchObject function
|
// Generalized fetchObject function
|
||||||
const fetchObject = async (id, type) => {
|
const fetchObject = async (id, type) => {
|
||||||
const fetchUrl = `${config.backendUrl}/${type}s/${id}`
|
const fetchUrl = `${config.backendUrl}/${getObjectEndpoint(type)}/${id}`
|
||||||
setFetchLoading(true)
|
setFetchLoading(true)
|
||||||
logger.debug('Fetching from ' + fetchUrl)
|
logger.debug('Fetching from ' + fetchUrl)
|
||||||
try {
|
try {
|
||||||
@ -738,21 +796,37 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
sorter = {},
|
sorter = {},
|
||||||
onDataChange
|
onDataChange
|
||||||
} = params
|
} = 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, {
|
logger.debug('Fetching table data from:', type, {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
filter,
|
newFilter,
|
||||||
sorter
|
sorter
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${config.backendUrl}/${type.toLowerCase()}s`,
|
`${config.backendUrl}/${getObjectEndpoint(type)}`,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
...filter,
|
...newFilter,
|
||||||
sort: sorter.field,
|
sort: sorter.field,
|
||||||
order: sorter.order
|
order: sorter.order
|
||||||
},
|
},
|
||||||
@ -799,7 +873,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${config.backendUrl}/${type.toLowerCase()}s/properties`,
|
`${config.backendUrl}/${getObjectEndpoint(type)}/properties`,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
...Object.keys(filter).reduce((acc, key) => {
|
...Object.keys(filter).reduce((acc, key) => {
|
||||||
@ -833,7 +907,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Update filament information
|
// Update filament information
|
||||||
const updateObject = async (id, type, value) => {
|
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)
|
logger.debug('Updating info for ' + id)
|
||||||
try {
|
try {
|
||||||
const response = await axios.put(updateUrl, value, {
|
const response = await axios.put(updateUrl, value, {
|
||||||
@ -855,7 +929,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Update multiple objects
|
// Update multiple objects
|
||||||
const updateMultipleObjects = async (type, 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)
|
logger.debug('Updating multiple objects for ' + type)
|
||||||
try {
|
try {
|
||||||
const response = await axios.put(updateUrl, objects, {
|
const response = await axios.put(updateUrl, objects, {
|
||||||
@ -877,7 +951,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Update filament information
|
// Update filament information
|
||||||
const deleteObject = async (id, type) => {
|
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)
|
logger.debug('Deleting object ID: ' + id)
|
||||||
try {
|
try {
|
||||||
const response = await axios.delete(deleteUrl, {
|
const response = await axios.delete(deleteUrl, {
|
||||||
@ -899,7 +973,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Update filament information
|
// Update filament information
|
||||||
const createObject = async (type, value) => {
|
const createObject = async (type, value) => {
|
||||||
const createUrl = `${config.backendUrl}/${type.toLowerCase()}s`
|
const createUrl = `${config.backendUrl}/${getObjectEndpoint(type)}`
|
||||||
logger.debug('Creating object...')
|
logger.debug('Creating object...')
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(createUrl, value, {
|
const response = await axios.post(createUrl, value, {
|
||||||
@ -920,7 +994,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Call a function on an object
|
// Call a function on an object
|
||||||
const sendObjectFunction = async (id, type, functionName, value = {}) => {
|
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}`)
|
logger.debug(`Calling object function ${functionName} for ${id} at ${url}`)
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(url, value, {
|
const response = await axios.post(url, value, {
|
||||||
@ -940,7 +1014,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getObjectFunction = async (id, type, functionName, params = {}) => {
|
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}`)
|
logger.debug(`Fetching object function ${functionName} for ${id} at ${url}`)
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(url, {
|
const response = await axios.get(url, {
|
||||||
@ -1165,7 +1239,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
if (objectType === 'history') {
|
if (objectType === 'history') {
|
||||||
statsUrl = `${config.backendUrl}/stats/history`
|
statsUrl = `${config.backendUrl}/stats/history`
|
||||||
} else {
|
} else {
|
||||||
statsUrl = `${config.backendUrl}/${objectType.toLowerCase()}s/stats`
|
statsUrl = `${config.backendUrl}/${getObjectEndpoint(objectType)}/stats`
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.get(statsUrl, {
|
const response = await axios.get(statsUrl, {
|
||||||
@ -1190,7 +1264,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
const encodedStartDate = encodeURIComponent(startDate.toISOString())
|
const encodedStartDate = encodeURIComponent(startDate.toISOString())
|
||||||
const encodedEndDate = encodeURIComponent(endDate.toISOString())
|
const encodedEndDate = encodeURIComponent(endDate.toISOString())
|
||||||
try {
|
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, {
|
const response = await axios.get(historyUrl, {
|
||||||
headers: {
|
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 { GCodeFile } from './models/GCodeFile'
|
||||||
import { Job } from './models/Job'
|
import { Job } from './models/Job'
|
||||||
import { Product } from './models/Product'
|
import { Product } from './models/Product'
|
||||||
|
import { ProductCategory } from './models/ProductCategory'
|
||||||
import { ProductSku } from './models/ProductSku'
|
import { ProductSku } from './models/ProductSku'
|
||||||
import { Part } from './models/Part.js'
|
import { Part } from './models/Part.js'
|
||||||
import { PartSku } from './models/PartSku.js'
|
import { PartSku } from './models/PartSku.js'
|
||||||
@ -56,6 +57,7 @@ export const objectModels = [
|
|||||||
GCodeFile,
|
GCodeFile,
|
||||||
Job,
|
Job,
|
||||||
Product,
|
Product,
|
||||||
|
ProductCategory,
|
||||||
ProductSku,
|
ProductSku,
|
||||||
Part,
|
Part,
|
||||||
PartSku,
|
PartSku,
|
||||||
@ -106,6 +108,7 @@ export {
|
|||||||
GCodeFile,
|
GCodeFile,
|
||||||
Job,
|
Job,
|
||||||
Product,
|
Product,
|
||||||
|
ProductCategory,
|
||||||
ProductSku,
|
ProductSku,
|
||||||
Part,
|
Part,
|
||||||
PartSku,
|
PartSku,
|
||||||
|
|||||||
@ -70,7 +70,15 @@ export const File = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
url: (id) => `/dashboard/management/files/info?fileId=${id}`,
|
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'],
|
filters: ['name', '_id', 'type', 'temp'],
|
||||||
sorters: ['name', 'type', 'size', 'createdAt', 'temp'],
|
sorters: ['name', 'type', 'size', 'createdAt', 'temp'],
|
||||||
group: ['type'],
|
group: ['type'],
|
||||||
@ -121,7 +129,7 @@ export const File = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
required: true,
|
required: true,
|
||||||
columnWidth: 120
|
columnWidth: 190
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
|
|||||||
@ -82,8 +82,23 @@ export const GCodeFile = {
|
|||||||
'gcodeFileInfo.hotPlateTemp',
|
'gcodeFileInfo.hotPlateTemp',
|
||||||
'updatedAt'
|
'updatedAt'
|
||||||
],
|
],
|
||||||
filters: ['_id', 'name', 'filament', 'filament._id', 'filamentSku', 'cost', 'updatedAt'],
|
filters: [
|
||||||
sorters: ['name', 'filament', 'filamentSku', 'cost', 'createdAt', 'updatedAt'],
|
'_id',
|
||||||
|
'name',
|
||||||
|
'filament',
|
||||||
|
'filament._id',
|
||||||
|
'filamentSku',
|
||||||
|
'cost',
|
||||||
|
'updatedAt'
|
||||||
|
],
|
||||||
|
sorters: [
|
||||||
|
'name',
|
||||||
|
'filament',
|
||||||
|
'filamentSku',
|
||||||
|
'cost',
|
||||||
|
'createdAt',
|
||||||
|
'updatedAt'
|
||||||
|
],
|
||||||
group: ['filament', 'filamentSku'],
|
group: ['filament', 'filamentSku'],
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
@ -255,6 +270,7 @@ export const GCodeFile = {
|
|||||||
label: 'Parts',
|
label: 'Parts',
|
||||||
type: 'objectChildren',
|
type: 'objectChildren',
|
||||||
objectType: 'part',
|
objectType: 'part',
|
||||||
|
size: 'medium',
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
name: 'part',
|
name: 'part',
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
|||||||
import EditIcon from '../../components/Icons/EditIcon'
|
import EditIcon from '../../components/Icons/EditIcon'
|
||||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||||
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
import OTPIcon from '../../components/Icons/OTPIcon'
|
import OTPIcon from '../../components/Icons/OTPIcon'
|
||||||
|
|
||||||
export const Host = {
|
export const Host = {
|
||||||
@ -60,6 +61,14 @@ export const Host = {
|
|||||||
icon: OTPIcon,
|
icon: OTPIcon,
|
||||||
url: (_id) =>
|
url: (_id) =>
|
||||||
`/dashboard/management/hosts/info?hostId=${_id}&action=hostOTP`
|
`/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'],
|
columns: ['_reference', 'name', 'state', 'tags', 'connectedAt'],
|
||||||
|
|||||||
@ -68,6 +68,7 @@ export const Product = {
|
|||||||
columns: [
|
columns: [
|
||||||
'_reference',
|
'_reference',
|
||||||
'name',
|
'name',
|
||||||
|
'productCategory',
|
||||||
'tags',
|
'tags',
|
||||||
'vendor',
|
'vendor',
|
||||||
'cost',
|
'cost',
|
||||||
@ -77,8 +78,30 @@ export const Product = {
|
|||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt'
|
'updatedAt'
|
||||||
],
|
],
|
||||||
filters: ['_id', 'name', 'type', 'color', 'vendor', 'cost', 'costWithTax', 'price', 'priceWithTax'],
|
filters: [
|
||||||
sorters: ['name', 'createdAt', 'type', 'vendor', 'cost', 'costWithTax', 'price', 'priceWithTax', 'updatedAt'],
|
'_id',
|
||||||
|
'name',
|
||||||
|
'productCategory',
|
||||||
|
'type',
|
||||||
|
'color',
|
||||||
|
'vendor',
|
||||||
|
'cost',
|
||||||
|
'costWithTax',
|
||||||
|
'price',
|
||||||
|
'priceWithTax'
|
||||||
|
],
|
||||||
|
sorters: [
|
||||||
|
'name',
|
||||||
|
'productCategory',
|
||||||
|
'createdAt',
|
||||||
|
'type',
|
||||||
|
'vendor',
|
||||||
|
'cost',
|
||||||
|
'costWithTax',
|
||||||
|
'price',
|
||||||
|
'priceWithTax',
|
||||||
|
'updatedAt'
|
||||||
|
],
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
name: '_id',
|
name: '_id',
|
||||||
@ -120,6 +143,15 @@ export const Product = {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 175
|
columnWidth: 175
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'productCategory',
|
||||||
|
label: 'Product Category',
|
||||||
|
required: true,
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'productCategory',
|
||||||
|
showHyperlink: true,
|
||||||
|
columnWidth: 200
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'vendor',
|
name: 'vendor',
|
||||||
label: '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'],
|
group: ['vendor'],
|
||||||
filters: ['vendor'],
|
filters: [
|
||||||
sorters: ['createdAt', 'state', 'updatedAt'],
|
'vendor',
|
||||||
|
'totalAmount',
|
||||||
|
'totalAmountWithTax',
|
||||||
|
'totalTaxAmount',
|
||||||
|
'shippingAmount',
|
||||||
|
'shippingAmountWithTax',
|
||||||
|
'grandTotalAmount'
|
||||||
|
],
|
||||||
|
sorters: [
|
||||||
|
'createdAt',
|
||||||
|
'state',
|
||||||
|
'updatedAt',
|
||||||
|
'totalAmount',
|
||||||
|
'totalAmountWithTax',
|
||||||
|
'totalTaxAmount',
|
||||||
|
'shippingAmount',
|
||||||
|
'shippingAmountWithTax',
|
||||||
|
'grandTotalAmount'
|
||||||
|
],
|
||||||
columns: [
|
columns: [
|
||||||
'_reference',
|
'_reference',
|
||||||
'state',
|
'state',
|
||||||
@ -260,7 +278,7 @@ export const PurchaseOrder = {
|
|||||||
prefix: '£',
|
prefix: '£',
|
||||||
roundNumber: 2,
|
roundNumber: 2,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 175
|
columnWidth: 185
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'completedAt',
|
name: 'completedAt',
|
||||||
@ -275,7 +293,7 @@ export const PurchaseOrder = {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
prefix: '£',
|
prefix: '£',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 175,
|
columnWidth: 215,
|
||||||
roundNumber: 2
|
roundNumber: 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -285,7 +303,7 @@ export const PurchaseOrder = {
|
|||||||
prefix: '£',
|
prefix: '£',
|
||||||
roundNumber: 2,
|
roundNumber: 2,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 150
|
columnWidth: 190
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'shippingAmountWithTax',
|
name: 'shippingAmountWithTax',
|
||||||
@ -294,7 +312,7 @@ export const PurchaseOrder = {
|
|||||||
prefix: '£',
|
prefix: '£',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
roundNumber: 2,
|
roundNumber: 2,
|
||||||
columnWidth: 200
|
columnWidth: 240
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'totalAmount',
|
name: 'totalAmount',
|
||||||
@ -303,7 +321,7 @@ export const PurchaseOrder = {
|
|||||||
prefix: '£',
|
prefix: '£',
|
||||||
roundNumber: 2,
|
roundNumber: 2,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 150
|
columnWidth: 170
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'grandTotalAmount',
|
name: 'grandTotalAmount',
|
||||||
@ -311,7 +329,7 @@ export const PurchaseOrder = {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
prefix: '£',
|
prefix: '£',
|
||||||
roundNumber: 2,
|
roundNumber: 2,
|
||||||
columnWidth: 175,
|
columnWidth: 215,
|
||||||
readOnly: true
|
readOnly: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@ -170,7 +170,7 @@ export const SalesOrder = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
group: ['client', 'marketplace'],
|
group: ['client'],
|
||||||
filters: ['client', 'marketplace'],
|
filters: ['client', 'marketplace'],
|
||||||
sorters: ['createdAt', 'state', 'updatedAt'],
|
sorters: ['createdAt', 'state', 'updatedAt'],
|
||||||
columns: [
|
columns: [
|
||||||
|
|||||||
@ -85,9 +85,9 @@ export const StockTransfer = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
url: (id) => `/dashboard/inventory/stocktransfers/info?stockTransferId=${id}`,
|
url: (id) => `/dashboard/inventory/stocktransfers/info?stockTransferId=${id}`,
|
||||||
filters: ['_id', 'state'],
|
filters: ['_id', 'name', 'state'],
|
||||||
sorters: ['createdAt', 'postedAt'],
|
sorters: ['name', 'createdAt', 'postedAt'],
|
||||||
columns: ['_reference', 'state', 'postedAt', 'createdAt', 'updatedAt'],
|
columns: ['_reference', 'name', 'state', 'postedAt', 'createdAt', 'updatedAt'],
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
name: '_id',
|
name: '_id',
|
||||||
@ -114,6 +114,14 @@ export const StockTransfer = {
|
|||||||
showCopy: true,
|
showCopy: true,
|
||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
columnWidth: 220,
|
||||||
|
columnFixed: 'left'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'state',
|
name: 'state',
|
||||||
label: 'State',
|
label: 'State',
|
||||||
@ -177,6 +185,38 @@ export const StockTransfer = {
|
|||||||
suffix: (row) =>
|
suffix: (row) =>
|
||||||
row?.fromStockType === 'filamentStock' ? 'g net' : null
|
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',
|
name: 'toStockLocation',
|
||||||
label: 'To location',
|
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 PartSkuInfo = lazy(() => import('../components/Dashboard/Management/PartSkus/PartSkuInfo.jsx'))
|
||||||
const Products = lazy(() => import('../components/Dashboard/Management/Products.jsx'))
|
const Products = lazy(() => import('../components/Dashboard/Management/Products.jsx'))
|
||||||
const ProductInfo = lazy(() => import('../components/Dashboard/Management/Products/ProductInfo.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 ProductSkus = lazy(() => import('../components/Dashboard/Management/ProductSkus.jsx'))
|
||||||
const ProductSkuInfo = lazy(() => import('../components/Dashboard/Management/ProductSkus/ProductSkuInfo.jsx'))
|
const ProductSkuInfo = lazy(() => import('../components/Dashboard/Management/ProductSkus/ProductSkuInfo.jsx'))
|
||||||
const Vendors = lazy(() => import('../components/Dashboard/Management/Vendors'))
|
const Vendors = lazy(() => import('../components/Dashboard/Management/Vendors'))
|
||||||
@ -79,6 +81,16 @@ const ManagementRoutes = [
|
|||||||
path='management/products/info'
|
path='management/products/info'
|
||||||
element={<ProductInfo />}
|
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' path='management/productskus' element={<ProductSkus />} />,
|
||||||
<Route
|
<Route
|
||||||
key='productskus-info'
|
key='productskus-info'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user