diff --git a/src/components/Dashboard/Management/CourierServices/CourierServiceInfo.jsx b/src/components/Dashboard/Management/CourierServices/CourierServiceInfo.jsx
index ceda405b..7eded5fd 100644
--- a/src/components/Dashboard/Management/CourierServices/CourierServiceInfo.jsx
+++ b/src/components/Dashboard/Management/CourierServices/CourierServiceInfo.jsx
@@ -8,10 +8,12 @@ import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
import InfoCollapse from '../../common/InfoCollapse'
import ObjectInfo from '../../common/ObjectInfo'
+import ObjectProperty from '../../common/ObjectProperty.jsx'
import ViewButton from '../../common/ViewButton'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
+import MarketplaceIcon from '../../../Icons/MarketplaceIcon.jsx'
import ObjectForm from '../../common/ObjectForm'
import EditButtons from '../../common/EditButtons'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
@@ -23,7 +25,7 @@ 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 { getModelByName } from '../../../../database/ObjectModels.js'
+import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('CourierServiceInfo')
@@ -40,6 +42,7 @@ const CourierServiceInfo = () => {
'CourierServiceInfo',
{
info: true,
+ marketplaces: true,
notes: true,
auditLogs: false
}
@@ -103,6 +106,7 @@ const CourierServiceInfo = () => {
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Courier Service Information' },
+ { key: 'marketplaces', label: 'Marketplaces' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
@@ -155,33 +159,56 @@ const CourierServiceInfo = () => {
loading={objectFormState.loading}
ref={actionHandlerRef}
>
- }
- active={collapseState.info}
- onToggle={(expanded) => updateCollapseState('info', expanded)}
- collapseKey='info'
+ {
+ setEditFormState((prev) => ({ ...prev, ...state }))
+ }}
>
- {
- setEditFormState((prev) => ({ ...prev, ...state }))
- }}
- >
- {({ loading, isEditing, objectData }) => (
-
- )}
-
-
+ {({ loading, isEditing, objectData }) => (
+
+ }
+ active={collapseState.info}
+ onToggle={(expanded) =>
+ updateCollapseState('info', expanded)
+ }
+ collapseKey='info'
+ >
+
+
+ }
+ active={collapseState.marketplaces}
+ onToggle={(expanded) =>
+ updateCollapseState('marketplaces', expanded)
+ }
+ collapseKey='marketplaces'
+ >
+
+
+
+ )}
+
{
_reference: false,
createdAt: false,
updatedAt: false,
- lastSyncedAt: false
+ lastSyncedAt: false,
+ stockQuantity: false
}}
isEditing={false}
objectData={objectData}
diff --git a/src/components/Dashboard/Sales/Listings/ListingInfo.jsx b/src/components/Dashboard/Sales/Listings/ListingInfo.jsx
index 390df921..025afb42 100644
--- a/src/components/Dashboard/Sales/Listings/ListingInfo.jsx
+++ b/src/components/Dashboard/Sales/Listings/ListingInfo.jsx
@@ -8,8 +8,10 @@ import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
import InfoCollapse from '../../common/InfoCollapse'
import ObjectInfo from '../../common/ObjectInfo'
+import ObjectProperty from '../../common/ObjectProperty.jsx'
import ViewButton from '../../common/ViewButton'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
+import TextIcon from '../../../Icons/TextIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import ListingVarientIcon from '../../../Icons/ListingVarientIcon.jsx'
@@ -25,7 +27,7 @@ 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 { getModelByName } from '../../../../database/ObjectModels.js'
+import { getModelByName, getModelProperty } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('ListingInfo')
@@ -47,6 +49,7 @@ const ListingInfo = () => {
const listingId = new URLSearchParams(location.search).get('listingId')
const [collapseState, updateCollapseState] = useCollapseState('ListingInfo', {
info: true,
+ description: true,
listingVarients: true,
notes: true,
auditLogs: false
@@ -109,6 +112,7 @@ const ListingInfo = () => {
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Listing Information' },
+ { key: 'description', label: 'Listing Description' },
{ key: 'listingVarients', label: 'Listing Varients' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
@@ -189,8 +193,29 @@ const ListingInfo = () => {
type='listing'
labelWidth={165}
objectData={objectData}
+ visibleProperties={{ description: false }}
/>
+ }
+ active={collapseState.description}
+ onToggle={(expanded) =>
+ updateCollapseState('description', expanded)
+ }
+ collapseKey='description'
+ >
+
+
+
+
}
diff --git a/src/components/Dashboard/Sales/Listings/NewListing.jsx b/src/components/Dashboard/Sales/Listings/NewListing.jsx
index cc31176c..1e6d36c9 100644
--- a/src/components/Dashboard/Sales/Listings/NewListing.jsx
+++ b/src/components/Dashboard/Sales/Listings/NewListing.jsx
@@ -53,7 +53,8 @@ const NewListing = ({ onOk, defaultValues }) => {
_reference: false,
createdAt: false,
updatedAt: false,
- lastSyncedAt: false
+ lastSyncedAt: false,
+ stockQuantity: false
}}
isEditing={false}
objectData={objectData}
diff --git a/src/components/Dashboard/Sales/Marketplaces/SyncMarketplace.jsx b/src/components/Dashboard/Sales/Marketplaces/SyncMarketplace.jsx
new file mode 100644
index 00000000..cdba2b4d
--- /dev/null
+++ b/src/components/Dashboard/Sales/Marketplaces/SyncMarketplace.jsx
@@ -0,0 +1,46 @@
+import { useState, useContext } from 'react'
+import PropTypes from 'prop-types'
+import { ApiServerContext } from '../../context/ApiServerContext'
+import { message } from 'antd'
+import MessageDialogView from '../../common/MessageDialogView.jsx'
+
+const SyncMarketplace = ({ onOk, objectData }) => {
+ const [syncLoading, setSyncLoading] = useState(false)
+ const { sendObjectFunction } = useContext(ApiServerContext)
+
+ const handleSync = async () => {
+ setSyncLoading(true)
+ try {
+ const result = await sendObjectFunction(
+ objectData._id,
+ 'Marketplace',
+ 'sync'
+ )
+ if (result?.success) {
+ message.success('Marketplace synced successfully')
+ onOk(result)
+ }
+ } catch (error) {
+ console.error('Error syncing marketplace:', error)
+ } finally {
+ setSyncLoading(false)
+ }
+ }
+
+ return (
+
+ )
+}
+
+SyncMarketplace.propTypes = {
+ onOk: PropTypes.func.isRequired,
+ objectData: PropTypes.object
+}
+
+export default SyncMarketplace
diff --git a/src/components/Dashboard/common/CustomSelect.jsx b/src/components/Dashboard/common/CustomSelect.jsx
index ec69b294..c83fe3d1 100644
--- a/src/components/Dashboard/common/CustomSelect.jsx
+++ b/src/components/Dashboard/common/CustomSelect.jsx
@@ -40,6 +40,8 @@ const CustomSelect = ({
options={Array.isArray(options) ? options : []}
value={value}
onChange={onChange}
+ showSearch
+ optionFilterProp='label'
{...rest}
/>
)
diff --git a/src/components/Dashboard/common/MarkdownDisplay.jsx b/src/components/Dashboard/common/MarkdownDisplay.jsx
index 4b5b80bd..6835ea8d 100644
--- a/src/components/Dashboard/common/MarkdownDisplay.jsx
+++ b/src/components/Dashboard/common/MarkdownDisplay.jsx
@@ -30,6 +30,7 @@ const BlockquoteComponent = ({ children }) => (
BlockquoteComponent.propTypes = { children: PropTypes.node }
const MarkdownDisplay = ({ content }) => {
+ const markdown = typeof content === 'string' ? content : ''
const components = {
h1: (props) =>
,
h2: (props) => ,
@@ -57,14 +58,14 @@ const MarkdownDisplay = ({ content }) => {
return (
- {content}
+ {markdown}
)
}
MarkdownDisplay.propTypes = {
- content: PropTypes.string.isRequired
+ content: PropTypes.string
}
export default MarkdownDisplay
diff --git a/src/components/Dashboard/common/ObjectProperty.jsx b/src/components/Dashboard/common/ObjectProperty.jsx
index e2947ef3..3ef0cb5a 100644
--- a/src/components/Dashboard/common/ObjectProperty.jsx
+++ b/src/components/Dashboard/common/ObjectProperty.jsx
@@ -138,8 +138,8 @@ const ObjectProperty = ({
scrollHeight,
...rest
}) => {
- if (value && typeof value == 'function' && objectData) {
- value = value(objectData, parentData)
+ if (typeof value === 'function') {
+ value = value(objectData || {}, parentData)
}
if (max && typeof max == 'function' && objectData) {
@@ -417,7 +417,7 @@ const ObjectProperty = ({
)
}
case 'markdown':
- if (value != null && value != '') {
+ if (typeof value === 'string' && value !== '') {
return
} else {
return (
diff --git a/src/database/models/Courier.js b/src/database/models/Courier.js
index 6a8a040d..70ced73f 100644
--- a/src/database/models/Courier.js
+++ b/src/database/models/Courier.js
@@ -153,6 +153,15 @@ export const Courier = {
required: true,
columnWidth: 180
},
+ {
+ name: 'externalReference',
+ label: 'External Reference',
+ type: 'text',
+ placeholder: 'RoyalMail',
+ extra: 'Marketplace shipping carrier code used when publishing to eBay.',
+ showCopy: true,
+ columnWidth: 200
+ },
{
name: 'name',
label: 'Name',
diff --git a/src/database/models/CourierService.js b/src/database/models/CourierService.js
index b91b2d05..fc540ee4 100644
--- a/src/database/models/CourierService.js
+++ b/src/database/models/CourierService.js
@@ -1,10 +1,12 @@
import { createElement, lazy } from 'react'
const CourierServiceInfo = lazy(
- () => import('../../components/Dashboard/Management/CourierServices/CourierServiceInfo')
+ () =>
+ import('../../components/Dashboard/Management/CourierServices/CourierServiceInfo')
)
const NewCourierService = lazy(
- () => import('../../components/Dashboard/Management/CourierServices/NewCourierService')
+ () =>
+ import('../../components/Dashboard/Management/CourierServices/NewCourierService')
)
const DeleteObject = lazy(
() => import('../../components/Dashboard/common/DeleteObject')
@@ -34,7 +36,11 @@ export const CourierService = {
label: 'New Courier Service',
icon: PlusIcon,
content: (objectData, { onOk } = {}) => {
- return createElement(NewCourierService, { defaultValues: objectData, onOk, reset: true })
+ return createElement(NewCourierService, {
+ defaultValues: objectData,
+ onOk,
+ reset: true
+ })
}
},
{
@@ -193,6 +199,48 @@ export const CourierService = {
required: true,
columnWidth: 200
},
+ {
+ name: 'marketplaces',
+ label: 'Marketplaces',
+ type: 'objectChildren',
+ required: false,
+ canAddRemove: true,
+ size: 'medium',
+ span: 2,
+ columns: ['marketplace', 'externalReference'],
+ properties: [
+ {
+ name: 'marketplace',
+ label: 'Marketplace',
+ type: 'object',
+ objectType: 'marketplace',
+ required: true,
+ showHyperlink: true,
+ columnWidth: 220
+ },
+ {
+ name: 'externalReference',
+ label: 'External Reference',
+ type: (objectData) =>
+ objectData?.marketplace?.provider === 'ebay' ? 'select' : 'text',
+ options: (objectData) => {
+ const codes =
+ objectData?.marketplace?.eBay?.availableShippingServices || []
+ const current = objectData?.externalReference
+ const unique = []
+ for (const code of [...codes, current]) {
+ if (code && !unique.includes(code)) unique.push(code)
+ }
+ return unique.map((code) => ({ label: code, value: code }))
+ },
+ extra:
+ 'For eBay listings, choose a shipping service code from the selected marketplace. Sync the marketplace if the list is empty.',
+ showCopy: true,
+ required: true,
+ columnWidth: 260
+ }
+ ]
+ },
{
name: 'deliveryTime',
label: 'Delivery Time',
diff --git a/src/database/models/Listing.js b/src/database/models/Listing.js
index 460c58ff..4a0dc5e6 100644
--- a/src/database/models/Listing.js
+++ b/src/database/models/Listing.js
@@ -152,9 +152,11 @@ export const Listing = {
'product',
'vendor',
'stockLocation',
+ 'stockQuantity',
'marketplace',
'courierServices',
'state',
+ 'condition',
'price',
'currency',
'lastSyncedAt',
@@ -165,10 +167,12 @@ export const Listing = {
'product',
'vendor',
'stockLocation',
+ 'stockQuantity',
'marketplace',
'courierServices',
'state',
'state.type',
+ 'condition',
'createdAt',
'updatedAt',
'_reference'
@@ -177,6 +181,7 @@ export const Listing = {
'title',
'vendor',
'state',
+ 'stockQuantity',
'price',
'lastSyncedAt',
'createdAt',
@@ -239,6 +244,16 @@ export const Listing = {
return objectData?.title
}
},
+ {
+ name: 'description',
+ label: 'Description',
+ type: 'markdown',
+ readOnly: false,
+ required: false,
+ span: 2,
+ extra:
+ 'Used as the eBay listing description. If empty, the title is sent instead.'
+ },
{
name: 'lastSyncedAt',
label: 'Last Synced',
@@ -282,6 +297,15 @@ export const Listing = {
required: true,
columnWidth: 200
},
+ {
+ name: 'stockQuantity',
+ label: 'Stock Quantity',
+ type: 'number',
+ min: 0,
+ readOnly: true,
+ required: false,
+ columnWidth: 150
+ },
{
name: 'marketplace',
label: 'Marketplace',
@@ -300,6 +324,34 @@ export const Listing = {
readOnly: true,
columnWidth: 260
},
+ {
+ name: 'condition',
+ label: 'Condition',
+ type: 'select',
+ required: true,
+ extra: 'Required by eBay for most categories. Stored in camel case and converted when publishing.',
+ options: [
+ { value: 'new', label: 'New' },
+ { value: 'likeNew', label: 'Like New' },
+ { value: 'newOther', label: 'New Other' },
+ { value: 'newWithDefects', label: 'New With Defects' },
+ { value: 'manufacturerRefurbished', label: 'Manufacturer Refurbished' },
+ { value: 'certifiedRefurbished', label: 'Certified Refurbished' },
+ { value: 'excellentRefurbished', label: 'Excellent Refurbished' },
+ { value: 'veryGoodRefurbished', label: 'Very Good Refurbished' },
+ { value: 'goodRefurbished', label: 'Good Refurbished' },
+ { value: 'sellerRefurbished', label: 'Seller Refurbished' },
+ { value: 'usedExcellent', label: 'Used Excellent' },
+ { value: 'usedVeryGood', label: 'Used Very Good' },
+ { value: 'usedGood', label: 'Used Good' },
+ { value: 'usedAcceptable', label: 'Used Acceptable' },
+ { value: 'forPartsOrNotWorking', label: 'For Parts Or Not Working' },
+ { value: 'preOwnedExcellent', label: 'Pre Owned Excellent' },
+ { value: 'preOwnedFair', label: 'Pre Owned Fair' }
+ ],
+ value: (objectData) => objectData?.condition || 'new',
+ columnWidth: 200
+ },
{
name: 'price',
label: 'Price',
diff --git a/src/database/models/ListingVarient.js b/src/database/models/ListingVarient.js
index c9d2f159..11f3cae7 100644
--- a/src/database/models/ListingVarient.js
+++ b/src/database/models/ListingVarient.js
@@ -143,6 +143,7 @@ export const ListingVarient = {
'listing',
'product',
'productSku',
+ 'stockQuantity',
'state',
'price',
'currency',
@@ -154,13 +155,14 @@ export const ListingVarient = {
'listing',
'product',
'productSku',
+ 'stockQuantity',
'state',
'state.type',
'createdAt',
'updatedAt',
'_reference'
],
- sorters: ['state', 'price', 'lastSyncedAt', 'createdAt', 'updatedAt', '_id'],
+ sorters: ['state', 'stockQuantity', 'price', 'lastSyncedAt', 'createdAt', 'updatedAt', '_id'],
group: ['listing', 'state'],
properties: [
{
@@ -232,6 +234,15 @@ export const ListingVarient = {
return { product: objectData?.listing?.product?._id }
}
},
+ {
+ name: 'stockQuantity',
+ label: 'Stock Quantity',
+ type: 'number',
+ min: 0,
+ readOnly: true,
+ required: false,
+ columnWidth: 150
+ },
{
name: 'state',
label: 'State',
diff --git a/src/database/models/Marketplace.js b/src/database/models/Marketplace.js
index 29402ba6..eb886bcd 100644
--- a/src/database/models/Marketplace.js
+++ b/src/database/models/Marketplace.js
@@ -6,6 +6,9 @@ const MarketplaceInfo = lazy(
const NewMarketplace = lazy(
() => import('../../components/Dashboard/Sales/Marketplaces/NewMarketplace')
)
+const SyncMarketplace = lazy(
+ () => import('../../components/Dashboard/Sales/Marketplaces/SyncMarketplace')
+)
const SyncListings = lazy(
() => import('../../components/Dashboard/Sales/Marketplaces/SyncListings')
)
@@ -42,7 +45,11 @@ export const Marketplace = {
label: 'New Marketplace',
icon: PlusIcon,
content: (objectData, { onOk } = {}) => {
- return createElement(NewMarketplace, { defaultValues: objectData, onOk, reset: true })
+ return createElement(NewMarketplace, {
+ defaultValues: objectData,
+ onOk,
+ reset: true
+ })
}
},
{
@@ -94,30 +101,66 @@ export const Marketplace = {
},
{ type: 'divider' },
{
- name: 'syncListings',
- type: 'modal',
- modalWidth: 520,
- label: 'Sync Listings',
+ name: 'sync',
+ label: 'Sync',
icon: ReloadIcon,
disabled: (objectData) => {
return objectData?.state?.type != 'ready'
},
- content: (objectData, { onOk } = {}) => {
- return createElement(SyncListings, { objectData, onOk })
- }
- },
- {
- name: 'syncOrders',
- type: 'modal',
- modalWidth: 520,
- label: 'Sync Orders',
- icon: ReloadIcon,
- disabled: (objectData) => {
- return objectData?.state?.type != 'ready'
- },
- content: (objectData, { onOk } = {}) => {
- return createElement(SyncOrders, { objectData, onOk })
- }
+ children: [
+ {
+ name: 'syncMarketplace',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Sync Marketplace',
+ icon: ReloadIcon,
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'ready'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(SyncMarketplace, { objectData, onOk })
+ }
+ },
+ {
+ name: 'syncListings',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Sync Listings',
+ icon: ReloadIcon,
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'ready'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(SyncListings, { objectData, onOk })
+ }
+ },
+ {
+ name: 'syncMarketplace',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Sync Marketplace',
+ icon: ReloadIcon,
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'ready'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(SyncMarketplace, { objectData, onOk })
+ }
+ },
+ {
+ name: 'syncOrders',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Sync Orders',
+ icon: ReloadIcon,
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'ready'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(SyncOrders, { objectData, onOk })
+ }
+ }
+ ]
},
{ type: 'divider' },
{
@@ -270,6 +313,17 @@ export const Marketplace = {
],
columnWidth: 150
},
+ {
+ name: 'eBay.availableShippingServices',
+ label: 'Available Shipping Services',
+ type: 'stringList',
+ readOnly: true,
+ required: false,
+ visible: (objectData) => objectData?.provider === 'ebay',
+ extra: 'Loaded from eBay GeteBayDetails when this marketplace is synced.',
+ span: 2,
+ columnWidth: 280
+ },
{
name: 'config.accessTokenExpiresAt',
label: 'Access Token Expires At',
diff --git a/src/database/models/Printer.js b/src/database/models/Printer.js
index 18849671..0c1ef202 100644
--- a/src/database/models/Printer.js
+++ b/src/database/models/Printer.js
@@ -56,7 +56,11 @@ export const Printer = {
label: 'New Printer',
icon: PlusIcon,
content: (objectData, { onOk } = {}) => {
- return createElement(NewPrinter, { defaultValues: objectData, onOk, reset: true })
+ return createElement(NewPrinter, {
+ defaultValues: objectData,
+ onOk,
+ reset: true
+ })
}
},
{
@@ -123,7 +127,7 @@ export const Printer = {
},
{ type: 'divider' },
{
- name: 'restartSubmenu',
+ name: 'restart',
label: 'Restart',
icon: ReloadIcon,
disabled: (objectData) => {
@@ -131,11 +135,11 @@ export const Printer = {
},
children: [
{
- name: 'restart',
+ name: 'restartPrinter',
type: 'modal',
modalWidth: 520,
modalCentered: true,
- label: 'Restart',
+ label: 'Restart Printer',
icon: ReloadIcon,
disabled: (objectData) => {
return objectData?.online == false