Implemented product skus.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
This commit is contained in:
parent
785e1054d0
commit
1882ec8e32
12
assets/icons/productskuicon.svg
Normal file
12
assets/icons/productskuicon.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?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="M17.414,66.503C16.713,66.492 16.015,66.297 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.993C61.171,38.751 61.96,40.114 61.96,41.653L61.96,60.463C61.96,63.797 59.253,66.504 55.919,66.504L17.543,66.503C17.5,66.504 17.457,66.504 17.414,66.503ZM56.328,40.656L56.5,40.558C56.447,40.512 56.431,40.489 56.316,40.42L46.646,34.839C46.14,34.532 45.551,34.532 45.045,34.839L35.382,40.42C35.26,40.489 35.229,40.52 35.145,40.589L35.239,40.643L55.919,40.643C56.057,40.643 56.193,40.647 56.328,40.656ZM27.985,40.643L28.117,40.567L17.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.589L15.655,45.672C16.137,42.819 18.622,40.643 21.612,40.643L27.985,40.643ZM57.891,46.683C57.891,45.595 57.008,44.711 55.919,44.711L21.612,44.711C20.523,44.711 19.64,45.595 19.64,46.683L19.64,60.463C19.64,61.551 20.523,62.435 21.612,62.435L55.919,62.435C57.008,62.435 57.891,61.551 57.891,60.463L57.891,46.683ZM29.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.94ZM33.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.986ZM31.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.132ZM15.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 transform="matrix(1.193399,0,0,1.216202,-14.746685,-3.63953)">
|
||||||
|
<path d="M34.973,52.259C37.641,52.259 39.196,50.992 39.196,49.001C39.196,47.433 38.218,46.581 36.139,46.206L35.167,46.031C34.202,45.857 33.786,45.649 33.786,45.214C33.786,44.778 34.188,44.429 34.979,44.429C35.59,44.429 36.039,44.61 36.354,45.093C36.696,45.609 37.078,45.817 37.681,45.817C38.392,45.81 38.861,45.374 38.861,44.731C38.861,44.51 38.821,44.329 38.74,44.134C38.238,42.927 36.863,42.25 34.946,42.25C32.646,42.25 30.97,43.477 30.97,45.421C30.97,46.923 32.023,47.895 33.947,48.237L34.919,48.411C36.005,48.606 36.381,48.8 36.381,49.256C36.381,49.732 35.858,50.081 35.006,50.081C34.369,50.081 33.806,49.886 33.471,49.41C33.089,48.874 32.727,48.706 32.191,48.706C31.487,48.706 30.97,49.176 30.97,49.859C30.97,50.081 31.017,50.309 31.118,50.53C31.534,51.475 32.794,52.259 34.973,52.259Z" style="fill-rule:nonzero;"/>
|
||||||
|
<path d="M41.677,52.259C42.582,52.259 43.098,51.723 43.098,50.771L43.098,49.558L43.943,48.686L46.115,51.442C46.577,52.032 46.993,52.259 47.603,52.259C48.381,52.259 48.971,51.663 48.971,50.885C48.971,50.483 48.763,50.054 48.274,49.45L46.182,46.863L48.18,44.731C48.582,44.302 48.729,43.993 48.729,43.564C48.729,42.834 48.126,42.25 47.382,42.25C46.899,42.25 46.531,42.438 46.122,42.894L43.152,46.253L43.098,46.253L43.098,43.739C43.098,42.787 42.582,42.25 41.677,42.25C40.772,42.25 40.256,42.787 40.256,43.739L40.256,50.771C40.256,51.723 40.772,52.259 41.677,52.259Z" style="fill-rule:nonzero;"/>
|
||||||
|
<path d="M54.14,52.259C56.694,52.259 58.403,50.852 58.403,48.646L58.403,43.739C58.403,42.787 57.887,42.25 56.982,42.25C56.077,42.25 55.561,42.787 55.561,43.739L55.561,48.331C55.561,49.35 55.058,49.906 54.14,49.906C53.221,49.906 52.718,49.35 52.718,48.331L52.718,43.739C52.718,42.787 52.202,42.25 51.297,42.25C50.392,42.25 49.876,42.787 49.876,43.739L49.876,48.646C49.876,50.852 51.585,52.259 54.14,52.259Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
@ -3,6 +3,7 @@ import DashboardSidebar from '../common/DashboardSidebar'
|
|||||||
import FilamentIcon from '../../Icons/FilamentIcon'
|
import FilamentIcon from '../../Icons/FilamentIcon'
|
||||||
import PartIcon from '../../Icons/PartIcon'
|
import PartIcon from '../../Icons/PartIcon'
|
||||||
import ProductIcon from '../../Icons/ProductIcon'
|
import ProductIcon from '../../Icons/ProductIcon'
|
||||||
|
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'
|
||||||
import NoteTypeIcon from '../../Icons/NoteTypeIcon'
|
import NoteTypeIcon from '../../Icons/NoteTypeIcon'
|
||||||
@ -42,6 +43,12 @@ const items = [
|
|||||||
label: 'Products',
|
label: 'Products',
|
||||||
path: '/dashboard/management/products'
|
path: '/dashboard/management/products'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'productSkus',
|
||||||
|
icon: <ProductSkuIcon />,
|
||||||
|
label: 'Product SKUs',
|
||||||
|
path: '/dashboard/management/productskus'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'vendors',
|
key: 'vendors',
|
||||||
icon: <VendorIcon />,
|
icon: <VendorIcon />,
|
||||||
@ -176,6 +183,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/productskus': 'productSkus',
|
||||||
'/dashboard/management/vendors': 'vendors',
|
'/dashboard/management/vendors': 'vendors',
|
||||||
'/dashboard/management/couriers': 'couriers',
|
'/dashboard/management/couriers': 'couriers',
|
||||||
'/dashboard/management/courierservices': 'courierServices',
|
'/dashboard/management/courierservices': 'courierServices',
|
||||||
|
|||||||
104
src/components/Dashboard/Management/ProductSkus.jsx
Normal file
104
src/components/Dashboard/Management/ProductSkus.jsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { useState, useRef } from 'react'
|
||||||
|
|
||||||
|
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
|
||||||
|
|
||||||
|
import NewProductSku from './ProductSkus/NewProductSku'
|
||||||
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
import ListIcon from '../../Icons/ListIcon'
|
||||||
|
import GridIcon from '../../Icons/GridIcon'
|
||||||
|
import useViewMode from '../hooks/useViewMode'
|
||||||
|
import ColumnViewButton from '../common/ColumnViewButton'
|
||||||
|
import ExportListButton from '../common/ExportListButton'
|
||||||
|
|
||||||
|
const ProductSkus = () => {
|
||||||
|
const tableRef = useRef()
|
||||||
|
|
||||||
|
const [newProductSkuOpen, setNewProductSkuOpen] = useState(false)
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useViewMode('productSkus')
|
||||||
|
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
useColumnVisibility('productSku')
|
||||||
|
|
||||||
|
const actionItems = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New Product SKU',
|
||||||
|
key: 'newProductSku',
|
||||||
|
icon: <PlusIcon />
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: 'Reload List',
|
||||||
|
key: 'reloadList',
|
||||||
|
icon: <ReloadIcon />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onClick: ({ key }) => {
|
||||||
|
if (key === 'reloadList') {
|
||||||
|
tableRef.current?.reload()
|
||||||
|
} else if (key === 'newProductSku') {
|
||||||
|
setNewProductSkuOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex vertical={'true'} gap='large'>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='small'>
|
||||||
|
<Dropdown menu={actionItems}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<ColumnViewButton
|
||||||
|
type='productSku'
|
||||||
|
loading={false}
|
||||||
|
visibleState={columnVisibility}
|
||||||
|
updateVisibleState={setColumnVisibility}
|
||||||
|
/>
|
||||||
|
<ExportListButton objectType='productSku' />
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||||
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<ObjectTable
|
||||||
|
ref={tableRef}
|
||||||
|
visibleColumns={columnVisibility}
|
||||||
|
type='productSku'
|
||||||
|
cards={viewMode === 'cards'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={newProductSkuOpen}
|
||||||
|
styles={{ content: { paddingBottom: '24px' } }}
|
||||||
|
footer={null}
|
||||||
|
width={600}
|
||||||
|
onCancel={() => {
|
||||||
|
setNewProductSkuOpen(false)
|
||||||
|
}}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
>
|
||||||
|
<NewProductSku
|
||||||
|
onOk={() => {
|
||||||
|
setNewProductSkuOpen(false)
|
||||||
|
tableRef.current?.reload()
|
||||||
|
}}
|
||||||
|
reset={newProductSkuOpen}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductSkus
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
|
const NewProductSku = ({ onOk, reset, defaultValues }) => {
|
||||||
|
return (
|
||||||
|
<NewObjectForm
|
||||||
|
type='productSku'
|
||||||
|
reset={reset}
|
||||||
|
defaultValues={defaultValues}
|
||||||
|
>
|
||||||
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='productSku'
|
||||||
|
column={1}
|
||||||
|
labelWidth={70}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Optional',
|
||||||
|
key: 'optional',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='productSku'
|
||||||
|
column={1}
|
||||||
|
labelWidth={100}
|
||||||
|
visibleProperties={{
|
||||||
|
description: true
|
||||||
|
}}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Summary',
|
||||||
|
key: 'summary',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='productSku'
|
||||||
|
column={1}
|
||||||
|
visibleProperties={{
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false,
|
||||||
|
_id: false
|
||||||
|
}}
|
||||||
|
labelWidth={100}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<WizardView
|
||||||
|
steps={steps}
|
||||||
|
loading={submitLoading}
|
||||||
|
formValid={formValid}
|
||||||
|
title='New Product SKU'
|
||||||
|
onSubmit={async () => {
|
||||||
|
const result = await handleSubmit()
|
||||||
|
if (result) {
|
||||||
|
onOk()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewProductSku.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
reset: PropTypes.bool,
|
||||||
|
defaultValues: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewProductSku
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
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 ObjectForm from '../../common/ObjectForm'
|
||||||
|
import EditButtons from '../../common/EditButtons'
|
||||||
|
import LockIndicator from '../../common/LockIndicator.jsx'
|
||||||
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.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 ProductSkuInfo = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const objectFormRef = useRef(null)
|
||||||
|
const actionHandlerRef = useRef(null)
|
||||||
|
const productSkuId = new URLSearchParams(location.search).get('productSkuId')
|
||||||
|
const [collapseState, updateCollapseState] = useCollapseState('ProductSkuInfo', {
|
||||||
|
info: true,
|
||||||
|
notes: true,
|
||||||
|
auditLogs: true
|
||||||
|
})
|
||||||
|
const [objectFormState, setEditFormState] = useState({
|
||||||
|
isEditing: false,
|
||||||
|
editLoading: false,
|
||||||
|
formValid: false,
|
||||||
|
lock: null,
|
||||||
|
loading: false,
|
||||||
|
objectData: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
reload: () => {
|
||||||
|
objectFormRef?.current?.fetchObject?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
edit: () => {
|
||||||
|
objectFormRef?.current?.startEditing?.()
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
cancelEdit: () => {
|
||||||
|
objectFormRef?.current?.cancelEditing?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
finishEdit: () => {
|
||||||
|
objectFormRef?.current?.handleUpdate?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
delete: () => {
|
||||||
|
objectFormRef?.current?.handleDelete?.()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
gap='large'
|
||||||
|
vertical='true'
|
||||||
|
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||||
|
>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='middle'>
|
||||||
|
<Space size='small'>
|
||||||
|
<ObjectActions
|
||||||
|
type='productSku'
|
||||||
|
id={productSkuId}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
<ViewButton
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
items={[
|
||||||
|
{ key: 'info', label: 'Product SKU Information' },
|
||||||
|
{ key: 'notes', label: 'Notes' },
|
||||||
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
|
]}
|
||||||
|
visibleState={collapseState}
|
||||||
|
updateVisibleState={updateCollapseState}
|
||||||
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='productSku'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
|
<DocumentPrintButton
|
||||||
|
type='productSku'
|
||||||
|
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 SKU Information'
|
||||||
|
icon={<InfoCircleIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||||
|
collapseKey='info'
|
||||||
|
>
|
||||||
|
<ObjectForm
|
||||||
|
id={productSkuId}
|
||||||
|
type='productSku'
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
ref={objectFormRef}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ loading, isEditing, objectData }) => (
|
||||||
|
<ObjectInfo
|
||||||
|
loading={loading}
|
||||||
|
isEditing={isEditing}
|
||||||
|
type='productSku'
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ObjectForm>
|
||||||
|
</InfoCollapse>
|
||||||
|
</ActionHandler>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Notes'
|
||||||
|
icon={<NoteIcon />}
|
||||||
|
active={collapseState.notes}
|
||||||
|
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||||
|
collapseKey='notes'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<NotesPanel _id={productSkuId} type='productSku' />
|
||||||
|
</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': productSkuId }}
|
||||||
|
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductSkuInfo
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { useRef, useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
|
import { Modal } from 'antd'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { Space, Flex, Card } from 'antd'
|
import { Space, Flex, Card } from 'antd'
|
||||||
import useCollapseState from '../../hooks/useCollapseState'
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
@ -22,6 +23,8 @@ import ScrollBox from '../../common/ScrollBox.jsx'
|
|||||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||||
import PartIcon from '../../../Icons/PartIcon.jsx'
|
import PartIcon from '../../../Icons/PartIcon.jsx'
|
||||||
|
import ProductSkuIcon from '../../../Icons/ProductSkuIcon.jsx'
|
||||||
|
import NewProductSku from '../ProductSkus/NewProductSku'
|
||||||
|
|
||||||
const ProductInfo = () => {
|
const ProductInfo = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
@ -31,9 +34,12 @@ const ProductInfo = () => {
|
|||||||
const [collapseState, updateCollapseState] = useCollapseState('ProductInfo', {
|
const [collapseState, updateCollapseState] = useCollapseState('ProductInfo', {
|
||||||
info: true,
|
info: true,
|
||||||
parts: true,
|
parts: true,
|
||||||
|
productSkus: true,
|
||||||
notes: true,
|
notes: true,
|
||||||
auditLogs: true
|
auditLogs: true
|
||||||
})
|
})
|
||||||
|
const [newProductSkuOpen, setNewProductSkuOpen] = useState(false)
|
||||||
|
const productSkusTableRef = useRef()
|
||||||
const [objectFormState, setEditFormState] = useState({
|
const [objectFormState, setEditFormState] = useState({
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
editLoading: false,
|
editLoading: false,
|
||||||
@ -48,6 +54,10 @@ const ProductInfo = () => {
|
|||||||
objectFormRef?.current?.fetchObject?.()
|
objectFormRef?.current?.fetchObject?.()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
newProductSku: () => {
|
||||||
|
setNewProductSkuOpen(true)
|
||||||
|
return false
|
||||||
|
},
|
||||||
edit: () => {
|
edit: () => {
|
||||||
objectFormRef?.current?.startEditing?.()
|
objectFormRef?.current?.startEditing?.()
|
||||||
return false
|
return false
|
||||||
@ -83,6 +93,7 @@ const ProductInfo = () => {
|
|||||||
items={[
|
items={[
|
||||||
{ key: 'info', label: 'Product Information' },
|
{ key: 'info', label: 'Product Information' },
|
||||||
{ key: 'parts', label: 'Product Parts' },
|
{ key: 'parts', label: 'Product Parts' },
|
||||||
|
{ key: 'productSkus', label: 'Product SKUs' },
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
]}
|
]}
|
||||||
@ -172,13 +183,54 @@ const ProductInfo = () => {
|
|||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
size='medium'
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Product SKUs'
|
||||||
|
icon={<ProductSkuIcon />}
|
||||||
|
active={collapseState.productSkus}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('productSkus', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='productSkus'
|
||||||
|
>
|
||||||
|
{objectFormState.loading ? (
|
||||||
|
<InfoCollapsePlaceholder />
|
||||||
|
) : (
|
||||||
|
<ObjectTable
|
||||||
|
ref={productSkusTableRef}
|
||||||
|
type='productSku'
|
||||||
|
masterFilter={{ product: productId }}
|
||||||
|
visibleColumns={{ product: false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</ObjectForm>
|
</ObjectForm>
|
||||||
</ActionHandler>
|
</ActionHandler>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={newProductSkuOpen}
|
||||||
|
styles={{ content: { paddingBottom: '24px' } }}
|
||||||
|
footer={null}
|
||||||
|
width={600}
|
||||||
|
onCancel={() => setNewProductSkuOpen(false)}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<NewProductSku
|
||||||
|
onOk={() => {
|
||||||
|
setNewProductSkuOpen(false)
|
||||||
|
productSkusTableRef.current?.reload?.()
|
||||||
|
}}
|
||||||
|
reset={newProductSkuOpen}
|
||||||
|
defaultValues={{
|
||||||
|
product: productId ? { _id: productId } : undefined
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
title='Notes'
|
title='Notes'
|
||||||
icon={<NoteIcon />}
|
icon={<NoteIcon />}
|
||||||
|
|||||||
8
src/components/Icons/ProductSkuIcon.jsx
Normal file
8
src/components/Icons/ProductSkuIcon.jsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import CustomIconSvg from '../../../assets/icons/productskuicon.svg?react'
|
||||||
|
|
||||||
|
const ProductSkuIcon = (props) => (
|
||||||
|
<Icon component={CustomIconSvg} {...props} />
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ProductSkuIcon
|
||||||
@ -5,6 +5,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 { ProductSku } from './models/ProductSku'
|
||||||
import { Part } from './models/Part.js'
|
import { Part } from './models/Part.js'
|
||||||
import { Vendor } from './models/Vendor'
|
import { Vendor } from './models/Vendor'
|
||||||
import { Courier } from './models/Courier'
|
import { Courier } from './models/Courier'
|
||||||
@ -45,6 +46,7 @@ export const objectModels = [
|
|||||||
GCodeFile,
|
GCodeFile,
|
||||||
Job,
|
Job,
|
||||||
Product,
|
Product,
|
||||||
|
ProductSku,
|
||||||
Part,
|
Part,
|
||||||
Vendor,
|
Vendor,
|
||||||
Courier,
|
Courier,
|
||||||
@ -86,6 +88,7 @@ export {
|
|||||||
GCodeFile,
|
GCodeFile,
|
||||||
Job,
|
Job,
|
||||||
Product,
|
Product,
|
||||||
|
ProductSku,
|
||||||
Part,
|
Part,
|
||||||
Vendor,
|
Vendor,
|
||||||
Courier,
|
Courier,
|
||||||
|
|||||||
@ -158,56 +158,14 @@ export const Client = {
|
|||||||
{
|
{
|
||||||
name: 'tags',
|
name: 'tags',
|
||||||
label: 'Tags',
|
label: 'Tags',
|
||||||
type: 'array',
|
type: 'tags',
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'address.building',
|
name: 'address',
|
||||||
label: 'Building',
|
label: 'Address',
|
||||||
type: 'text',
|
type: 'address',
|
||||||
readOnly: false,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'address.addressLine1',
|
|
||||||
label: 'Address Line 1',
|
|
||||||
type: 'text',
|
|
||||||
readOnly: false,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'address.addressLine2',
|
|
||||||
label: 'Address Line 2',
|
|
||||||
type: 'text',
|
|
||||||
readOnly: false,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'address.city',
|
|
||||||
label: 'City',
|
|
||||||
type: 'text',
|
|
||||||
readOnly: false,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'address.state',
|
|
||||||
label: 'State',
|
|
||||||
type: 'text',
|
|
||||||
readOnly: false,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'address.postcode',
|
|
||||||
label: 'Postcode',
|
|
||||||
type: 'text',
|
|
||||||
readOnly: false,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'address.country',
|
|
||||||
label: 'Country',
|
|
||||||
type: 'country',
|
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import ReloadIcon from '../../components/Icons/ReloadIcon'
|
|||||||
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 PlusIcon from '../../components/Icons/PlusIcon'
|
||||||
|
|
||||||
export const Product = {
|
export const Product = {
|
||||||
name: 'product',
|
name: 'product',
|
||||||
@ -56,9 +57,31 @@ export const Product = {
|
|||||||
visible: (objectData) => {
|
visible: (objectData) => {
|
||||||
return objectData?._isEditing && objectData?._isEditing == true
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'newProductSku',
|
||||||
|
label: 'New Product SKU',
|
||||||
|
type: 'button',
|
||||||
|
icon: PlusIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/products/info?productId=${_id}&action=newProductSku`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
columns: ['_reference', 'name', 'tags', 'vendor', 'price', 'createdAt', 'updatedAt'],
|
columns: [
|
||||||
|
'_reference',
|
||||||
|
'name',
|
||||||
|
'tags',
|
||||||
|
'vendor',
|
||||||
|
'price',
|
||||||
|
'createdAt',
|
||||||
|
'updatedAt'
|
||||||
|
],
|
||||||
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor'],
|
filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor'],
|
||||||
sorters: ['name', 'createdAt', 'type', 'vendor', 'cost', 'updatedAt'],
|
sorters: ['name', 'createdAt', 'type', 'vendor', 'cost', 'updatedAt'],
|
||||||
properties: [
|
properties: [
|
||||||
|
|||||||
123
src/database/models/ProductSku.js
Normal file
123
src/database/models/ProductSku.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import ProductSkuIcon from '../../components/Icons/ProductSkuIcon'
|
||||||
|
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 ReloadIcon from '../../components/Icons/ReloadIcon'
|
||||||
|
|
||||||
|
export const ProductSku = {
|
||||||
|
name: 'productSku',
|
||||||
|
label: 'Product SKU',
|
||||||
|
prefix: 'SKU',
|
||||||
|
icon: ProductSkuIcon,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'info',
|
||||||
|
label: 'Info',
|
||||||
|
default: true,
|
||||||
|
row: true,
|
||||||
|
icon: InfoCircleIcon,
|
||||||
|
url: (_id) => `/dashboard/management/productskus/info?productSkuId=${_id}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reload',
|
||||||
|
label: 'Reload',
|
||||||
|
icon: ReloadIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/productskus/info?productSkuId=${_id}&action=reload`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'edit',
|
||||||
|
label: 'Edit',
|
||||||
|
row: true,
|
||||||
|
icon: EditIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/productskus/info?productSkuId=${_id}&action=edit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'finishEdit',
|
||||||
|
label: 'Save Edits',
|
||||||
|
icon: CheckIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/productskus/info?productSkuId=${_id}&action=finishEdit`,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelEdit',
|
||||||
|
label: 'Cancel Edits',
|
||||||
|
icon: XMarkIcon,
|
||||||
|
url: (_id) =>
|
||||||
|
`/dashboard/management/productskus/info?productSkuId=${_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/productskus/info?productSkuId=${_id}&action=delete`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
url: (id) => `/dashboard/management/productskus/info?productSkuId=${id}`,
|
||||||
|
columns: ['_reference', 'sku', 'product', 'name', 'createdAt', 'updatedAt'],
|
||||||
|
filters: ['_id', 'sku', 'product', 'product._id', 'name'],
|
||||||
|
sorters: ['sku', 'product', 'name', 'createdAt', 'updatedAt'],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'productSku',
|
||||||
|
showCopy: true,
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
required: true,
|
||||||
|
type: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'product',
|
||||||
|
label: 'Product',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'product',
|
||||||
|
required: true,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sku',
|
||||||
|
label: 'SKU',
|
||||||
|
required: true,
|
||||||
|
type: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
label: 'Description',
|
||||||
|
required: false,
|
||||||
|
type: 'text'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@ const Parts = lazy(() => import('../components/Dashboard/Management/Parts.jsx'))
|
|||||||
const PartInfo = lazy(() => import('../components/Dashboard/Management/Parts/PartInfo.jsx'))
|
const PartInfo = lazy(() => import('../components/Dashboard/Management/Parts/PartInfo.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 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'))
|
const Vendors = lazy(() => import('../components/Dashboard/Management/Vendors'))
|
||||||
const VendorInfo = lazy(() => import('../components/Dashboard/Management/Vendors/VendorInfo'))
|
const VendorInfo = lazy(() => import('../components/Dashboard/Management/Vendors/VendorInfo'))
|
||||||
const Materials = lazy(() => import('../components/Dashboard/Management/Materials'))
|
const Materials = lazy(() => import('../components/Dashboard/Management/Materials'))
|
||||||
@ -60,6 +62,12 @@ const ManagementRoutes = [
|
|||||||
path='management/products/info'
|
path='management/products/info'
|
||||||
element={<ProductInfo />}
|
element={<ProductInfo />}
|
||||||
/>,
|
/>,
|
||||||
|
<Route key='productskus' path='management/productskus' element={<ProductSkus />} />,
|
||||||
|
<Route
|
||||||
|
key='productskus-info'
|
||||||
|
path='management/productskus/info'
|
||||||
|
element={<ProductSkuInfo />}
|
||||||
|
/>,
|
||||||
<Route key='vendors' path='management/vendors' element={<Vendors />} />,
|
<Route key='vendors' path='management/vendors' element={<Vendors />} />,
|
||||||
<Route key='hosts' path='management/hosts' element={<Hosts />} />,
|
<Route key='hosts' path='management/hosts' element={<Hosts />} />,
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user