diff --git a/assets/icons/marketplaceicon.svg b/assets/icons/marketplaceicon.svg
new file mode 100644
index 0000000..58f212e
--- /dev/null
+++ b/assets/icons/marketplaceicon.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/components/Dashboard/Sales/Marketplaces.jsx b/src/components/Dashboard/Sales/Marketplaces.jsx
new file mode 100644
index 0000000..9beab06
--- /dev/null
+++ b/src/components/Dashboard/Sales/Marketplaces.jsx
@@ -0,0 +1,107 @@
+import { useState, useRef } from 'react'
+import { Button, Flex, Space, Modal, Dropdown } from 'antd'
+import NewMarketplace from './Marketplaces/NewMarketplace'
+import ObjectTable from '../common/ObjectTable'
+import PlusIcon from '../../Icons/PlusIcon'
+import ReloadIcon from '../../Icons/ReloadIcon'
+import useColumnVisibility from '../hooks/useColumnVisibility'
+import ObjectTableViewButton from '../common/ObjectTableViewButton'
+import FilterSidebarButton from '../common/FilterSidebarButton'
+import useViewMode from '../hooks/useViewMode'
+import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
+import ColumnViewButton from '../common/ColumnViewButton'
+import ExportListButton from '../common/ExportListButton'
+
+const Marketplaces = () => {
+ const [newMarketplaceOpen, setNewMarketplaceOpen] = useState(false)
+ const tableRef = useRef()
+
+ const [viewMode, setViewMode] = useViewMode('marketplaces')
+
+ const [columnVisibility, setColumnVisibility] =
+ useColumnVisibility('marketplaces')
+
+ const [showFilterSidebar, setShowFilterSidebar] =
+ useFilterSidebarVisibility('Marketplaces')
+
+ const actionItems = {
+ items: [
+ {
+ label: 'New Marketplace',
+ key: 'newMarketplace',
+ icon:
+ },
+ { type: 'divider' },
+ {
+ label: 'Reload List',
+ key: 'reloadList',
+ icon:
+ }
+ ],
+ onClick: ({ key }) => {
+ if (key === 'reloadList') {
+ tableRef.current?.reload()
+ } else if (key === 'newMarketplace') {
+ setNewMarketplaceOpen(true)
+ }
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ setShowFilterSidebar(!showFilterSidebar)}
+ />
+
+
+
+
+
+ {
+ setNewMarketplaceOpen(false)
+ }}
+ destroyOnHidden={true}
+ >
+ {
+ setNewMarketplaceOpen(false)
+ tableRef.current?.reload()
+ }}
+ reset={newMarketplaceOpen}
+ />
+
+ >
+ )
+}
+
+export default Marketplaces
diff --git a/src/components/Dashboard/Sales/Marketplaces/MarketplaceInfo.jsx b/src/components/Dashboard/Sales/Marketplaces/MarketplaceInfo.jsx
new file mode 100644
index 0000000..9bd461f
--- /dev/null
+++ b/src/components/Dashboard/Sales/Marketplaces/MarketplaceInfo.jsx
@@ -0,0 +1,200 @@
+import { useRef, useState } from 'react'
+import { useLocation } from 'react-router-dom'
+import { Space, Flex } from 'antd'
+import loglevel from 'loglevel'
+import config from '../../../../config'
+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'
+import { Card } from 'antd'
+
+const log = loglevel.getLogger('MarketplaceInfo')
+log.setLevel(config.logLevel)
+
+const MarketplaceInfo = () => {
+ const location = useLocation()
+ const objectFormRef = useRef(null)
+ const actionHandlerRef = useRef(null)
+ const marketplaceId = new URLSearchParams(location.search).get('marketplaceId')
+ const [collapseState, updateCollapseState] = useCollapseState('MarketplaceInfo', {
+ 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?.handleFetchObject?.()
+ 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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ 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}
+ />
+
+
+
+
+
+ }
+ active={collapseState.info}
+ onToggle={(expanded) => updateCollapseState('info', expanded)}
+ collapseKey='info'
+ >
+ {
+ setEditFormState((prev) => ({ ...prev, ...state }))
+ }}
+ >
+ {({ loading, isEditing, objectData }) => (
+
+ )}
+
+
+
+ }
+ active={collapseState.notes}
+ onToggle={(expanded) => updateCollapseState('notes', expanded)}
+ collapseKey='notes'
+ >
+
+
+
+
+ }
+ active={collapseState.auditLogs}
+ onToggle={(expanded) =>
+ updateCollapseState('auditLogs', expanded)
+ }
+ collapseKey='auditLogs'
+ >
+ {objectFormState.loading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ >
+ )
+}
+
+export default MarketplaceInfo
diff --git a/src/components/Dashboard/Sales/Marketplaces/NewMarketplace.jsx b/src/components/Dashboard/Sales/Marketplaces/NewMarketplace.jsx
new file mode 100644
index 0000000..0221a1b
--- /dev/null
+++ b/src/components/Dashboard/Sales/Marketplaces/NewMarketplace.jsx
@@ -0,0 +1,103 @@
+import PropTypes from 'prop-types'
+import ObjectInfo from '../../common/ObjectInfo'
+import NewObjectForm from '../../common/NewObjectForm'
+import WizardView from '../../common/WizardView'
+
+const NewMarketplace = ({ onOk, defaultValues }) => {
+ return (
+
+ {({ handleSubmit, submitLoading, objectData, formValid }) => {
+ const steps = [
+ {
+ title: 'Required',
+ key: 'required',
+ content: (
+
+ )
+ },
+ {
+ title: 'API Configuration',
+ key: 'config',
+ content: (
+
+ )
+ },
+ {
+ title: 'Summary',
+ key: 'summary',
+ content: (
+
+ )
+ }
+ ]
+ return (
+ {
+ const result = await handleSubmit()
+ if (result) {
+ onOk()
+ }
+ }}
+ />
+ )
+ }}
+
+ )
+}
+
+NewMarketplace.propTypes = {
+ onOk: PropTypes.func.isRequired,
+ reset: PropTypes.bool,
+ defaultValues: PropTypes.object
+}
+
+export default NewMarketplace
diff --git a/src/components/Dashboard/Sales/SalesSidebar.jsx b/src/components/Dashboard/Sales/SalesSidebar.jsx
index 9b89a29..da7dd8d 100644
--- a/src/components/Dashboard/Sales/SalesSidebar.jsx
+++ b/src/components/Dashboard/Sales/SalesSidebar.jsx
@@ -2,8 +2,8 @@ import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import ClientIcon from '../../Icons/ClientIcon'
import SalesIcon from '../../Icons/SalesIcon'
-
import SalesOrderIcon from '../../Icons/SalesOrderIcon'
+import MarketplaceIcon from '../../Icons/MarketplaceIcon'
const items = [
{
@@ -24,13 +24,20 @@ const items = [
label: 'Sales Orders',
icon: ,
path: '/dashboard/sales/salesorders'
+ },
+ {
+ key: 'marketplaces',
+ label: 'Marketplaces',
+ icon: ,
+ path: '/dashboard/sales/marketplaces'
}
]
const routeKeyMap = {
'/dashboard/sales/overview': 'overview',
'/dashboard/sales/clients': 'clients',
- '/dashboard/sales/salesorders': 'salesorders'
+ '/dashboard/sales/salesorders': 'salesorders',
+ '/dashboard/sales/marketplaces': 'marketplaces'
}
const SalesSidebar = (props) => {
diff --git a/src/components/Dashboard/common/ObjectInfo.jsx b/src/components/Dashboard/common/ObjectInfo.jsx
index 8f96fc1..4ec1d97 100644
--- a/src/components/Dashboard/common/ObjectInfo.jsx
+++ b/src/components/Dashboard/common/ObjectInfo.jsx
@@ -63,6 +63,11 @@ const ObjectInfo = ({
items = items.filter((item) => {
const propertyName = item.name
+ // Support property.visible as a function (objectData) => boolean
+ if (typeof item.visible === 'function') {
+ const visible = item.visible(objectData || combinedObjectData || {})
+ if (!visible) return false
+ }
if (isWhitelistMode) {
// Whitelist mode: only show properties that are explicitly set to true
return visibleProperties[propertyName] === true
diff --git a/src/components/Icons/MarketplaceIcon.jsx b/src/components/Icons/MarketplaceIcon.jsx
new file mode 100644
index 0000000..985e064
--- /dev/null
+++ b/src/components/Icons/MarketplaceIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/marketplaceicon.svg?react'
+
+const MarketplaceIcon = (props) =>
+
+export default MarketplaceIcon
diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js
index ddb3043..3acf151 100644
--- a/src/database/ObjectModels.js
+++ b/src/database/ObjectModels.js
@@ -39,6 +39,7 @@ import { Invoice } from './models/Invoice.js'
import { Payment } from './models/Payment.js'
import { Client } from './models/Client.js'
import { SalesOrder } from './models/SalesOrder.js'
+import { Marketplace } from './models/Marketplace.js'
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
export const objectModels = [
@@ -82,7 +83,8 @@ export const objectModels = [
Invoice,
Payment,
Client,
- SalesOrder
+ SalesOrder,
+ Marketplace
]
// Re-export individual models for direct access
@@ -127,7 +129,8 @@ export {
Invoice,
Payment,
Client,
- SalesOrder
+ SalesOrder,
+ Marketplace
}
export function getModelByName(name, ignoreCase = false) {
diff --git a/src/database/models/Client.js b/src/database/models/Client.js
index 62993f1..29f8e56 100644
--- a/src/database/models/Client.js
+++ b/src/database/models/Client.js
@@ -84,6 +84,7 @@ export const Client = {
'email',
'phone',
'active',
+ 'marketplace',
'createdAt',
'updatedAt'
],
@@ -186,6 +187,16 @@ export const Client = {
readOnly: false,
required: false,
columnWidth: 250
+ },
+ {
+ name: 'marketplace',
+ label: 'Marketplace',
+ type: 'object',
+ objectType: 'marketplace',
+ showHyperlink: true,
+ readOnly: false,
+ required: false,
+ columnWidth: 200
}
]
}
diff --git a/src/database/models/Marketplace.js b/src/database/models/Marketplace.js
new file mode 100644
index 0000000..74c7ffb
--- /dev/null
+++ b/src/database/models/Marketplace.js
@@ -0,0 +1,244 @@
+import MarketplaceIcon from '../../components/Icons/MarketplaceIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
+import EditIcon from '../../components/Icons/EditIcon'
+import CheckIcon from '../../components/Icons/CheckIcon'
+import XMarkIcon from '../../components/Icons/XMarkIcon'
+import ReloadIcon from '../../components/Icons/ReloadIcon'
+import BinIcon from '../../components/Icons/BinIcon'
+
+export const Marketplace = {
+ name: 'marketplace',
+ label: 'Marketplace',
+ prefix: 'MKT',
+ icon: MarketplaceIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/sales/marketplaces/info?marketplaceId=${_id}`
+ },
+ {
+ name: 'reload',
+ label: 'Reload',
+ icon: ReloadIcon,
+ url: (_id) =>
+ `/dashboard/sales/marketplaces/info?marketplaceId=${_id}&action=reload`
+ },
+ {
+ name: 'edit',
+ label: 'Edit',
+ row: true,
+ icon: EditIcon,
+ url: (_id) =>
+ `/dashboard/sales/marketplaces/info?marketplaceId=${_id}&action=edit`,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ }
+ },
+ {
+ name: 'finishEdit',
+ label: 'Save Edits',
+ icon: CheckIcon,
+ url: (_id) =>
+ `/dashboard/sales/marketplaces/info?marketplaceId=${_id}&action=finishEdit`,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'cancelEdit',
+ label: 'Cancel Edits',
+ icon: XMarkIcon,
+ url: (_id) =>
+ `/dashboard/sales/marketplaces/info?marketplaceId=${_id}&action=cancelEdit`,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ { type: 'divider' },
+ {
+ name: 'delete',
+ label: 'Delete',
+ icon: BinIcon,
+ danger: true,
+ url: (_id) =>
+ `/dashboard/sales/marketplaces/info?marketplaceId=${_id}&action=delete`
+ }
+ ],
+ columns: [
+ '_reference',
+ 'name',
+ 'provider',
+ 'active',
+ 'createdAt',
+ 'updatedAt'
+ ],
+ filters: ['name', '_id', 'provider', 'active', 'createdAt', 'updatedAt'],
+ sorters: ['name', 'provider', 'active', 'createdAt', 'updatedAt', '_id'],
+ group: ['provider'],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ columnFixed: 'left',
+ type: 'id',
+ objectType: 'marketplace',
+ showCopy: true,
+ columnWidth: 140
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: '_reference',
+ label: 'Reference',
+ type: 'reference',
+ columnFixed: 'left',
+ objectType: 'marketplace',
+ showCopy: true,
+ readOnly: true
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ columnFixed: 'left',
+ required: true,
+ type: 'text',
+ columnWidth: 200
+ },
+ {
+ name: 'active',
+ label: 'Active',
+ type: 'bool',
+ readOnly: false,
+ required: true,
+ columnWidth: 125
+ },
+ {
+ name: 'provider',
+ label: 'Provider',
+ type: 'select',
+ required: true,
+ options: [
+ { value: 'ebay', label: 'eBay' },
+ { value: 'etsy', label: 'Etsy' },
+ { value: 'tiktokShop', label: 'TikTok Shop' }
+ ],
+ columnWidth: 150
+ },
+
+ {
+ name: 'config.appId',
+ label: 'App ID',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'ebay'
+ },
+ {
+ name: 'config.certId',
+ label: 'Cert ID',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'ebay'
+ },
+ {
+ name: 'config.devId',
+ label: 'Dev ID',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'ebay'
+ },
+ {
+ name: 'config.userToken',
+ label: 'User Token',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'ebay'
+ },
+ {
+ name: 'config.siteId',
+ label: 'Site ID',
+ type: 'text',
+ readOnly: false,
+ required: false,
+ columnWidth: 120,
+ visible: (objectData) => objectData?.provider === 'ebay'
+ },
+ {
+ name: 'config.accessToken',
+ label: 'Access Token',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'etsy'
+ },
+ {
+ name: 'config.refreshToken',
+ label: 'Refresh Token',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'etsy'
+ },
+ {
+ name: 'config.shopId',
+ label: 'Shop ID',
+ type: 'text',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'etsy'
+ },
+ {
+ name: 'config.appKey',
+ label: 'App Key',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'tiktokShop'
+ },
+ {
+ name: 'config.appSecret',
+ label: 'App Secret',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'tiktokShop'
+ },
+ {
+ name: 'config.shopCipher',
+ label: 'Shop Cipher',
+ type: 'text',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'tiktokShop'
+ }
+ ]
+}
diff --git a/src/database/models/SalesOrder.js b/src/database/models/SalesOrder.js
index 76522bb..3b1dd60 100644
--- a/src/database/models/SalesOrder.js
+++ b/src/database/models/SalesOrder.js
@@ -170,13 +170,14 @@ export const SalesOrder = {
}
}
],
- group: ['client'],
- filters: ['client'],
+ group: ['client', 'marketplace'],
+ filters: ['client', 'marketplace'],
sorters: ['createdAt', 'state', 'updatedAt'],
columns: [
'_reference',
'state',
'client',
+ 'marketplace',
'totalAmount',
'totalAmountWithTax',
'totalTaxAmount',
@@ -245,6 +246,16 @@ export const SalesOrder = {
showHyperlink: true,
columnWidth: 200
},
+ {
+ name: 'marketplace',
+ label: 'Marketplace',
+ type: 'object',
+ objectType: 'marketplace',
+ showHyperlink: true,
+ readOnly: false,
+ required: false,
+ columnWidth: 200
+ },
{
name: 'confirmedAt',
label: 'Confirmed At',
diff --git a/src/routes/SalesRoutes.jsx b/src/routes/SalesRoutes.jsx
index 89d5a38..b6f1b45 100644
--- a/src/routes/SalesRoutes.jsx
+++ b/src/routes/SalesRoutes.jsx
@@ -16,6 +16,12 @@ const SalesOrderInfo = lazy(
const SalesOverview = lazy(
() => import('../components/Dashboard/Sales/SalesOverview.jsx')
)
+const Marketplaces = lazy(
+ () => import('../components/Dashboard/Sales/Marketplaces.jsx')
+)
+const MarketplaceInfo = lazy(
+ () => import('../components/Dashboard/Sales/Marketplaces/MarketplaceInfo.jsx')
+)
const SalesRoutes = [
}
+ />,
+ } />,
+ }
/>
]