Compare commits

...

4 Commits

30 changed files with 870 additions and 644 deletions

2
Jenkinsfile vendored
View File

@ -78,7 +78,7 @@ def buildOnLabel(label, buildCommand) {
}
stage("Archive Artifacts (${label})") {
archiveArtifacts artifacts: 'app_dist/**/*.dmg, app_dist/**/*.exe', fingerprint: true
archiveArtifacts artifacts: 'app_dist/**/*.dmg, app_dist/**/*.exe, app_dist/**/*.pkg', fingerprint: true
}
}
}

BIN
assets/dmg/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -148,11 +148,23 @@
"arm64"
]
},
{
"target": "pkg",
"arch": [
"arm64"
]
},
{
"target": "dmg",
"arch": [
"x64"
]
},
{
"target": "pkg",
"arch": [
"x64"
]
}
],
"mergeASARs": true,
@ -169,6 +181,32 @@
]
}
},
"dmg": {
"background": "assets/dmg/background.png",
"iconSize": 100,
"window": {
"width": 540,
"height": 380
},
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
},
"pkg": {
"installLocation": "/Applications",
"mustClose": [
"com.tombutcher.farmcontrol"
]
},
"win": {
"target": "nsis"
},

View File

@ -6,6 +6,7 @@ const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
let win
let sidebarViewMenuSections = []
const PROTOCOL_PREFIX = 'farmcontrol://'
@ -40,6 +41,76 @@ function sendNavigateToRenderer(redirectPath) {
deliver()
}
function toElectronSidebarMenuItems(items = []) {
return items
.map((item) => {
if (item?.type === 'divider') {
return { type: 'separator' }
}
const menuItem = {
label: item.label
}
if (item?.children && Array.isArray(item.children) && item.children.length) {
menuItem.submenu = toElectronSidebarMenuItems(item.children)
} else if (item?.path) {
menuItem.click = () => sendNavigateToRenderer(item.path)
} else {
menuItem.enabled = false
}
return menuItem
})
.filter(Boolean)
}
function buildApplicationMenuTemplate() {
const env = (process.env.NODE_ENV || 'development').trim()
const viewSubmenu = sidebarViewMenuSections.map((section) => ({
label: section.label,
submenu: toElectronSidebarMenuItems(section.items || [])
}))
if (viewSubmenu.length === 0) {
viewSubmenu.push({ label: 'No sidebar items available', enabled: false })
}
if (env === 'development') {
viewSubmenu.push(
{ type: 'separator' },
{
label: 'Toggle Developer Tools',
accelerator:
process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click: () => {
if (win && !win.isDestroyed()) {
win.webContents.toggleDevTools()
}
}
}
)
}
const template = [
{ role: 'fileMenu' },
{ role: 'editMenu' },
{ label: 'View', submenu: viewSubmenu },
{ role: 'windowMenu' }
]
if (process.platform === 'darwin') {
template.unshift({ role: 'appMenu' })
}
return template
}
function applyApplicationMenu() {
const menu = Menu.buildFromTemplate(buildApplicationMenuTemplate())
Menu.setApplicationMenu(menu)
}
export function handleDeepLink(url) {
if (!url?.startsWith(`${PROTOCOL_PREFIX}app`)) return
const redirectPath = url.replace(`${PROTOCOL_PREFIX}app`, '') || '/'
@ -118,29 +189,7 @@ export function createWindow() {
}
})
// Set up custom menu bar
const env = (process.env.NODE_ENV || 'development').trim()
if (env === 'development') {
const devMenu = [
{
label: 'Developer',
submenu: [
{
label: 'Toggle Developer Tools',
accelerator:
process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click: () => {
win.webContents.toggleDevTools()
}
}
]
}
]
const menu = Menu.buildFromTemplate(devMenu)
Menu.setApplicationMenu(menu)
} else {
Menu.setApplicationMenu(null)
}
applyApplicationMenu()
// For development, load from localhost; for production, load the built index.html
if (process.env.ELECTRON_START_URL) {
@ -208,6 +257,16 @@ export function setupMainWindowIPC() {
}
return true
})
ipcMain.handle('set-sidebar-view-menu', (event, sidebarSections) => {
if (!Array.isArray(sidebarSections)) {
return false
}
sidebarViewMenuSections = sidebarSections
applyApplicationMenu()
return true
})
}
export function setupMainWindowAppEvents(app) {

View File

@ -1,12 +1,13 @@
import { useContext, useEffect, useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Flex, Card, Alert } from 'antd'
import { Flex, Card, Alert, Button } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { customAlphabet } from 'nanoid'
import AuthParticles from './AppParticles'
import FarmControlLogo from '../Logos/FarmControlLogo'
import ExclamationOctagonIcon from '../Icons/ExclamationOctagonIcon'
import CheckIcon from '../Icons/CheckIcon'
import ReloadIcon from '../Icons/ReloadIcon'
import { ApiServerContext } from '../Dashboard/context/ApiServerContext'
const createLaunchSession = customAlphabet(
@ -24,6 +25,10 @@ const AuthLaunch = () => {
const [launchErrorMessage, setLaunchErrorMessage] = useState('')
const [launchSuccess, setLaunchSuccess] = useState(false)
const handleRefresh = () => {
window.location.reload()
}
useEffect(() => {
let cancelled = false
const redirect = new URLSearchParams(location.search).get('redirect')
@ -168,12 +173,19 @@ const AuthLaunch = () => {
/>
)}
{launchError && (
<Alert
message={launchErrorMessage}
icon={<ExclamationOctagonIcon />}
type='error'
showIcon
/>
<>
<Alert
message={launchErrorMessage}
icon={<ExclamationOctagonIcon />}
type='error'
showIcon
/>
<Button
icon={<ReloadIcon />}
onClick={handleRefresh}
size='large'
/>
</>
)}
{launchSuccess && (
<Alert

View File

@ -1,50 +1,14 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import { Typography } from 'antd'
const { Text } = Typography
const items = [
{
key: 'sessionstorage',
label: 'Session Storage',
icon: <Text>🗃</Text>,
path: '/dashboard/developer/sessionstorage'
},
{
key: 'authcontextdebug',
label: 'Auth Debug',
icon: <Text>🔐</Text>,
path: '/dashboard/developer/authcontextdebug'
},
{
key: 'apicontextdebug',
label: 'API Debug',
icon: <Text>🌐</Text>,
path: '/dashboard/developer/apicontextdebug'
}
]
const routeKeyMap = {
'/dashboard/developer/sessionstorage': 'sessionstorage',
'/dashboard/developer/authcontextdebug': 'authcontextdebug',
'/dashboard/developer/apicontextdebug': 'apicontextdebug'
}
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
const DeveloperSidebar = (props) => {
const location = useLocation()
const selectedKey = (() => {
const match = Object.keys(routeKeyMap).find((path) => {
const pathSplit = path.split('/')
const locationPathSplit = location.pathname.split('/')
if (pathSplit.length > locationPathSplit.length) return false
for (let i = 0; i < pathSplit.length; i++) {
if (pathSplit[i] !== locationPathSplit[i]) return false
}
return true
})
return match ? routeKeyMap[match] : 'sessionstorage'
})()
const includeDev = import.meta.env.MODE === 'development'
const items = getSidebarItems('developer', { includeDev })
const selectedKey = getSidebarSelectedKey('developer', location.pathname, {
includeDev
})
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
}

View File

@ -1,51 +1,14 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import InvoiceIcon from '../../Icons/InvoiceIcon'
import PaymentIcon from '../../Icons/PaymentIcon'
import FinanceIcon from '../../Icons/FinanceIcon'
const items = [
{
key: 'overview',
label: 'Overview',
icon: <FinanceIcon />,
path: '/dashboard/finance/overview'
},
{ type: 'divider' },
{
key: 'invoices',
label: 'Invoices',
icon: <InvoiceIcon />,
path: '/dashboard/finance/invoices'
},
{
key: 'payments',
label: 'Payments',
icon: <PaymentIcon />,
path: '/dashboard/finance/payments'
}
]
const routeKeyMap = {
'/dashboard/finance/overview': 'overview',
'/dashboard/finance/invoices': 'invoices',
'/dashboard/finance/payments': 'payments'
}
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
const FinanceSidebar = (props) => {
const location = useLocation()
const selectedKey = (() => {
const match = Object.keys(routeKeyMap).find((path) => {
const pathSplit = path.split('/')
const locationPathSplit = location.pathname.split('/')
if (pathSplit.length > locationPathSplit.length) return false
for (let i = 0; i < pathSplit.length; i++) {
if (pathSplit[i] !== locationPathSplit[i]) return false
}
return true
})
return match ? routeKeyMap[match] : 'overview'
})()
const includeDev = import.meta.env.MODE === 'development'
const items = getSidebarItems('finance', { includeDev })
const selectedKey = getSidebarSelectedKey('finance', location.pathname, {
includeDev
})
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
}

View File

@ -1,118 +1,14 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import FilamentStockIcon from '../../Icons/FilamentStockIcon'
import PartStockIcon from '../../Icons/PartStockIcon'
import ProductStockIcon from '../../Icons/ProductStockIcon'
import StockEventIcon from '../../Icons/StockEventIcon'
import StockAuditIcon from '../../Icons/StockAuditIcon'
import PurchaseOrderIcon from '../../Icons/PurchaseOrderIcon'
import ShipmentIcon from '../../Icons/ShipmentIcon'
import OrderItemIcon from '../../Icons/OrderItemIcon'
import InventoryIcon from '../../Icons/InventoryIcon'
import StockLocationIcon from '../../Icons/StockLocationIcon'
import StockTransferIcon from '../../Icons/StockTransferIcon'
const items = [
{
key: 'overview',
label: 'Overview',
icon: <InventoryIcon />,
path: '/dashboard/inventory/overview'
},
{ type: 'divider' },
{
key: 'filamentstocks',
label: 'Filament Stocks',
icon: <FilamentStockIcon />,
path: '/dashboard/inventory/filamentstocks'
},
{
key: 'partstocks',
label: 'Part Stocks',
icon: <PartStockIcon />,
path: '/dashboard/inventory/partstocks'
},
{
key: 'productstocks',
label: 'Product Stocks',
icon: <ProductStockIcon />,
path: '/dashboard/inventory/productstocks'
},
{ type: 'divider' },
{
key: 'purchaseorders',
label: 'Purchase Orders',
icon: <PurchaseOrderIcon />,
path: '/dashboard/inventory/purchaseorders'
},
{ type: 'divider' },
{
key: 'orderitems',
label: 'Order Items',
icon: <OrderItemIcon />,
path: '/dashboard/inventory/orderitems'
},
{
key: 'shipments',
label: 'Shipments',
icon: <ShipmentIcon />,
path: '/dashboard/inventory/shipments'
},
{ type: 'divider' },
{
key: 'stocklocations',
label: 'Stock Locations',
icon: <StockLocationIcon />,
path: '/dashboard/inventory/stocklocations'
},
{
key: 'stockevents',
label: 'Stock Events',
icon: <StockEventIcon />,
path: '/dashboard/inventory/stockevents'
},
{
key: 'stockaudits',
label: 'Stock Audits',
icon: <StockAuditIcon />,
path: '/dashboard/inventory/stockaudits'
},
{
key: 'stocktransfers',
label: 'Stock Transfers',
icon: <StockTransferIcon />,
path: '/dashboard/inventory/stocktransfers'
}
]
const routeKeyMap = {
'/dashboard/inventory/overview': 'overview',
'/dashboard/inventory/filamentstocks': 'filamentstocks',
'/dashboard/inventory/partstocks': 'partstocks',
'/dashboard/inventory/productstocks': 'productstocks',
'/dashboard/inventory/stocklocations': 'stocklocations',
'/dashboard/inventory/stocktransfers': 'stocktransfers',
'/dashboard/inventory/stockevents': 'stockevents',
'/dashboard/inventory/stockaudits': 'stockaudits',
'/dashboard/inventory/purchaseorders': 'purchaseorders',
'/dashboard/inventory/orderitems': 'orderitems',
'/dashboard/inventory/shipments': 'shipments'
}
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
const InventorySidebar = (props) => {
const location = useLocation()
const selectedKey = (() => {
const match = Object.keys(routeKeyMap).find((path) => {
const pathSplit = path.split('/')
const locationPathSplit = location.pathname.split('/')
if (pathSplit.length > locationPathSplit.length) return false
for (let i = 0; i < pathSplit.length; i++) {
if (pathSplit[i] !== locationPathSplit[i]) return false
}
return true
})
return match ? routeKeyMap[match] : 'overview'
})()
const includeDev = import.meta.env.MODE === 'development'
const items = getSidebarItems('inventory', { includeDev })
const selectedKey = getSidebarSelectedKey('inventory', location.pathname, {
includeDev
})
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
}

View File

@ -1,244 +1,14 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import FilamentIcon from '../../Icons/FilamentIcon'
import FilamentSkuIcon from '../../Icons/FilamentSkuIcon'
import PartIcon from '../../Icons/PartIcon'
import PartSkuIcon from '../../Icons/PartSkuIcon'
import ProductIcon from '../../Icons/ProductIcon'
import ProductCategoryIcon from '../../Icons/ProductCategoryIcon'
import ProductSkuIcon from '../../Icons/ProductSkuIcon'
import VendorIcon from '../../Icons/VendorIcon'
import MaterialIcon from '../../Icons/MaterialIcon'
import NoteTypeIcon from '../../Icons/NoteTypeIcon'
import SettingsIcon from '../../Icons/SettingsIcon'
import AuditLogIcon from '../../Icons/AuditLogIcon'
import DeveloperIcon from '../../Icons/DeveloperIcon'
import PersonIcon from '../../Icons/PersonIcon'
import HostIcon from '../../Icons/HostIcon'
import DocumentPrinterIcon from '../../Icons/DocumentPrinterIcon'
import DocumentTemplateIcon from '../../Icons/DocumentTemplateIcon'
import DocumentIcon from '../../Icons/DocumentIcon'
import DocumentSizeIcon from '../../Icons/DocumentSizeIcon'
import DocumentJobIcon from '../../Icons/DocumentJobIcon'
import FileIcon from '../../Icons/FileIcon'
import CourierIcon from '../../Icons/CourierIcon'
import CourierServiceIcon from '../../Icons/CourierServiceIcon'
import TaxRateIcon from '../../Icons/TaxRateIcon'
import TaxRecordIcon from '../../Icons/TaxRecordIcon'
import AppPasswordIcon from '../../Icons/AppPasswordIcon'
const items = [
{
key: 'filaments',
icon: <FilamentIcon />,
label: 'Filaments',
path: '/dashboard/management/filaments'
},
{
key: 'filamentSkus',
icon: <FilamentSkuIcon />,
label: 'Filament SKUs',
path: '/dashboard/management/filamentskus'
},
{
key: 'parts',
icon: <PartIcon />,
label: 'Parts',
path: '/dashboard/management/parts'
},
{
key: 'partSkus',
icon: <PartSkuIcon />,
label: 'Part SKUs',
path: '/dashboard/management/partskus'
},
{
key: 'products',
icon: <ProductIcon />,
label: 'Products',
path: '/dashboard/management/products'
},
{
key: 'productCategories',
icon: <ProductCategoryIcon />,
label: 'Product Categories',
path: '/dashboard/management/productcategories'
},
{
key: 'productSkus',
icon: <ProductSkuIcon />,
label: 'Product SKUs',
path: '/dashboard/management/productskus'
},
{
key: 'vendors',
icon: <VendorIcon />,
label: 'Vendors',
path: '/dashboard/management/vendors'
},
{
key: 'materials',
icon: <MaterialIcon />,
label: 'Materials',
path: '/dashboard/management/materials'
},
{ type: 'divider' },
{
key: 'couriers',
icon: <CourierIcon />,
label: 'Couriers',
path: '/dashboard/management/couriers'
},
{
key: 'courierServices',
icon: <CourierServiceIcon />,
label: 'Courier Services',
path: '/dashboard/management/courierservices'
},
{ type: 'divider' },
{
key: 'taxRates',
icon: <TaxRateIcon />,
label: 'Tax Rates',
path: '/dashboard/management/taxrates'
},
{
key: 'taxRecords',
icon: <TaxRecordIcon />,
label: 'Tax Records',
path: '/dashboard/management/taxrecords'
},
{ type: 'divider' },
{
key: 'noteTypes',
icon: <NoteTypeIcon />,
label: 'Note Types',
path: '/dashboard/management/notetypes'
},
{
key: 'documents',
icon: <DocumentIcon />,
label: 'Documents',
children: [
{
key: 'documentPrinters',
icon: <DocumentPrinterIcon />,
label: 'Document Printers',
path: '/dashboard/management/documentprinters'
},
{
key: 'documentJobs',
icon: <DocumentJobIcon />,
label: 'Document Jobs',
path: '/dashboard/management/documentjobs'
},
{
key: 'documentTemplates',
icon: <DocumentTemplateIcon />,
label: 'Document Templates',
path: '/dashboard/management/documenttemplates'
},
{
key: 'documentSizes',
icon: <DocumentSizeIcon />,
label: 'Document Sizes',
path: '/dashboard/management/documentsizes'
}
]
},
{ type: 'divider' },
{
key: 'hosts',
icon: <HostIcon />,
label: 'Hosts',
path: '/dashboard/management/hosts'
},
{ type: 'divider' },
{
key: 'users',
icon: <PersonIcon />,
label: 'Users',
path: '/dashboard/management/users'
},
{
key: 'appPasswords',
icon: <AppPasswordIcon />,
label: 'App Passwords',
path: '/dashboard/management/apppasswords'
},
{
key: 'settings',
icon: <SettingsIcon />,
label: 'Settings',
path: '/dashboard/management/settings'
},
{
key: 'files',
icon: <FileIcon />,
label: 'Files',
path: '/dashboard/management/files'
},
{
key: 'auditLogs',
icon: <AuditLogIcon />,
label: 'Audit Logs',
path: '/dashboard/management/auditlogs'
}
]
if (import.meta.env.MODE === 'development') {
items.push(
{ type: 'divider' },
{
key: 'developer',
icon: <DeveloperIcon />,
label: 'Developer',
path: '/dashboard/developer/sessionstorage'
}
)
}
const routeKeyMap = {
'/dashboard/management/filaments': 'filaments',
'/dashboard/management/filamentskus': 'filamentSkus',
'/dashboard/management/parts': 'parts',
'/dashboard/management/partskus': 'partSkus',
'/dashboard/management/users': 'users',
'/dashboard/management/apppasswords': 'appPasswords',
'/dashboard/management/products': 'products',
'/dashboard/management/productcategories': 'productCategories',
'/dashboard/management/productskus': 'productSkus',
'/dashboard/management/vendors': 'vendors',
'/dashboard/management/couriers': 'couriers',
'/dashboard/management/courierservices': 'courierServices',
'/dashboard/management/taxrates': 'taxRates',
'/dashboard/management/taxrecords': 'taxRecords',
'/dashboard/management/materials': 'materials',
'/dashboard/management/notetypes': 'noteTypes',
'/dashboard/management/settings': 'settings',
'/dashboard/management/auditlogs': 'auditLogs',
'/dashboard/management/files': 'files',
'/dashboard/management/hosts': 'hosts',
'/dashboard/management/documentsizes': 'documentSizes',
'/dashboard/management/documentprinters': 'documentPrinters',
'/dashboard/management/documenttemplates': 'documentTemplates',
'/dashboard/management/documentjobs': 'documentJobs'
}
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
const ManagementSidebar = (props) => {
const location = useLocation()
const selectedKey = (() => {
const match = Object.keys(routeKeyMap).find((path) => {
const pathSplit = path.split('/')
const locationPathSplit = location.pathname.split('/')
if (pathSplit.length > locationPathSplit.length) return false
for (let i = 0; i < pathSplit.length; i++) {
if (pathSplit[i] !== locationPathSplit[i]) return false
}
return true
})
return match ? routeKeyMap[match] : 'filaments'
})()
const includeDev = import.meta.env.MODE === 'development'
const items = getSidebarItems('management', { includeDev })
const selectedKey = getSidebarSelectedKey('management', location.pathname, {
includeDev
})
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
}

View File

@ -1,67 +1,14 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import ProductionIcon from '../../Icons/ProductionIcon'
import PrinterIcon from '../../Icons/PrinterIcon'
import JobIcon from '../../Icons/JobIcon'
import GCodeFileIcon from '../../Icons/GCodeFileIcon'
import SubJobIcon from '../../Icons/SubJobIcon'
const items = [
{
key: 'overview',
icon: <ProductionIcon />,
label: 'Overview',
path: '/dashboard/production/overview'
},
{ type: 'divider' },
{
key: 'printers',
icon: <PrinterIcon />,
label: 'Printers',
path: '/dashboard/production/printers'
},
{
key: 'jobs',
icon: <JobIcon />,
label: 'Jobs',
path: '/dashboard/production/jobs'
},
{
key: 'subJobs',
icon: <SubJobIcon />,
label: 'Sub Jobs',
path: '/dashboard/production/subjobs'
},
{
key: 'gcodeFiles',
icon: <GCodeFileIcon />,
label: 'GCode Files',
path: '/dashboard/production/gcodefiles'
}
]
const routeKeyMap = {
'/dashboard/production/overview': 'overview',
'/dashboard/production/printers': 'printers',
'/dashboard/production/jobs': 'jobs',
'/dashboard/production/subjobs': 'subJobs',
'/dashboard/production/gcodefiles': 'gcodeFiles'
}
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
const ProductionSidebar = (props) => {
const location = useLocation()
const selectedKey = (() => {
const match = Object.keys(routeKeyMap).find((path) => {
const pathSplit = path.split('/')
const locationPathSplit = location.pathname.split('/')
if (pathSplit.length > locationPathSplit.length) return false
for (let i = 0; i < pathSplit.length; i++) {
if (pathSplit[i] !== locationPathSplit[i]) return false
}
return true
})
return match ? routeKeyMap[match] : 'overview'
})()
const includeDev = import.meta.env.MODE === 'development'
const items = getSidebarItems('production', { includeDev })
const selectedKey = getSidebarSelectedKey('production', location.pathname, {
includeDev
})
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
}

View File

@ -1,68 +1,14 @@
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'
import ListingIcon from '../../Icons/ListingIcon'
const items = [
{
key: 'overview',
label: 'Overview',
icon: <SalesIcon />,
path: '/dashboard/sales/overview'
},
{ type: 'divider' },
{
key: 'clients',
label: 'Clients',
icon: <ClientIcon />,
path: '/dashboard/sales/clients'
},
{
key: 'salesorders',
label: 'Sales Orders',
icon: <SalesOrderIcon />,
path: '/dashboard/sales/salesorders'
},
{
key: 'marketplaces',
label: 'Marketplaces',
icon: <MarketplaceIcon />,
path: '/dashboard/sales/marketplaces'
},
{
key: 'listings',
label: 'Listings',
icon: <ListingIcon />,
path: '/dashboard/sales/listings'
}
]
const routeKeyMap = {
'/dashboard/sales/overview': 'overview',
'/dashboard/sales/clients': 'clients',
'/dashboard/sales/salesorders': 'salesorders',
'/dashboard/sales/marketplaces': 'marketplaces',
'/dashboard/sales/listings': 'listings',
'/dashboard/sales/listingvarients': 'listings'
}
import { getSidebarItems, getSidebarSelectedKey } from '../../../database/Sidebars'
const SalesSidebar = (props) => {
const location = useLocation()
const selectedKey = (() => {
const match = Object.keys(routeKeyMap).find((path) => {
const pathSplit = path.split('/')
const locationPathSplit = location.pathname.split('/')
if (pathSplit.length > locationPathSplit.length) return false
for (let i = 0; i < pathSplit.length; i++) {
if (pathSplit[i] !== locationPathSplit[i]) return false
}
return true
})
return match ? routeKeyMap[match] : 'overview'
})()
const includeDev = import.meta.env.MODE === 'development'
const items = getSidebarItems('sales', { includeDev })
const selectedKey = getSidebarSelectedKey('sales', location.pathname, {
includeDev
})
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
}

View File

@ -38,6 +38,10 @@ import SettingsIcon from '../../Icons/SettingsIcon'
import DeveloperIcon from '../../Icons/DeveloperIcon'
import { ElectronContext } from '../context/ElectronContext'
import DashboardWindowButtons from './DashboardWindowButtons'
import {
getSidebarDefaultPath,
getSidebarMenuSections
} from '../../../database/Sidebars'
const { Text } = Typography
@ -57,7 +61,8 @@ const DashboardNavigation = () => {
icon: <ProductionIcon />
})
const isMobile = useMediaQuery({ maxWidth: 768 })
const { platform, isElectron, isFullScreen } = useContext(ElectronContext)
const { platform, isElectron, isFullScreen, setSidebarViewMenu } =
useContext(ElectronContext)
const mainMenuItems = useMemo(
() => [
@ -117,19 +122,16 @@ const DashboardNavigation = () => {
}, [connecting, connected])
const handleMainMenuClick = ({ key }) => {
if (key === 'production') {
navigate('/dashboard/production/overview')
} else if (key === 'inventory') {
navigate('/dashboard/inventory/overview')
} else if (key === 'finance') {
navigate('/dashboard/finance/overview')
} else if (key === 'sales') {
navigate('/dashboard/sales/overview')
} else if (key === 'management') {
navigate('/dashboard/management/filaments')
}
navigate(getSidebarDefaultPath(key, { includeDev: import.meta.env.DEV }))
}
useEffect(() => {
if (!isElectron || !setSidebarViewMenu) return
const includeDev = import.meta.env.DEV
const sections = getSidebarMenuSections({ includeDev })
setSidebarViewMenu(sections)
}, [isElectron, setSidebarViewMenu])
const showAppLogo =
(isElectron && platform == 'darwin' && isFullScreen == true) ||
(isElectron && platform != 'darwin')

View File

@ -7,6 +7,7 @@ import { useMediaQuery } from 'react-responsive'
import { useNavigate } from 'react-router-dom'
import PropTypes from 'prop-types'
import { ElectronContext } from '../context/ElectronContext'
import { getSidebarIconNode } from '../../Icons/sidebarIconMap'
const { Sider } = Layout
const DashboardSidebar = ({
@ -47,7 +48,7 @@ const DashboardSidebar = ({
const mappedItem = {
key: item.key,
icon: item.icon,
icon: item.icon || getSidebarIconNode(item.iconKey),
label: item.label
}

View File

@ -1,4 +1,4 @@
import { createContext, useEffect, useState } from 'react'
import { createContext, useCallback, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { useNavigate } from 'react-router-dom'
@ -144,6 +144,14 @@ const ElectronProvider = ({ children }) => {
}
}
const setSidebarViewMenu = useCallback(
async (sections) => {
if (!electronAvailable || !ipcRenderer) return false
return await ipcRenderer.invoke('set-sidebar-view-menu', sections)
},
[electronAvailable]
)
return (
<ElectronContext.Provider
value={{
@ -159,7 +167,8 @@ const ElectronProvider = ({ children }) => {
clearAuthSession,
getToken,
setToken,
resizeSpotlightWindow
resizeSpotlightWindow,
setSidebarViewMenu
}}
>
{children}

View File

@ -0,0 +1,113 @@
import ProductionIcon from './ProductionIcon'
import PrinterIcon from './PrinterIcon'
import JobIcon from './JobIcon'
import GCodeFileIcon from './GCodeFileIcon'
import SubJobIcon from './SubJobIcon'
import InventoryIcon from './InventoryIcon'
import FilamentStockIcon from './FilamentStockIcon'
import PartStockIcon from './PartStockIcon'
import ProductStockIcon from './ProductStockIcon'
import StockEventIcon from './StockEventIcon'
import StockAuditIcon from './StockAuditIcon'
import PurchaseOrderIcon from './PurchaseOrderIcon'
import ShipmentIcon from './ShipmentIcon'
import OrderItemIcon from './OrderItemIcon'
import StockLocationIcon from './StockLocationIcon'
import StockTransferIcon from './StockTransferIcon'
import SalesIcon from './SalesIcon'
import ClientIcon from './ClientIcon'
import SalesOrderIcon from './SalesOrderIcon'
import MarketplaceIcon from './MarketplaceIcon'
import ListingIcon from './ListingIcon'
import FinanceIcon from './FinanceIcon'
import InvoiceIcon from './InvoiceIcon'
import PaymentIcon from './PaymentIcon'
import FilamentIcon from './FilamentIcon'
import FilamentSkuIcon from './FilamentSkuIcon'
import PartIcon from './PartIcon'
import PartSkuIcon from './PartSkuIcon'
import ProductIcon from './ProductIcon'
import ProductCategoryIcon from './ProductCategoryIcon'
import ProductSkuIcon from './ProductSkuIcon'
import VendorIcon from './VendorIcon'
import MaterialIcon from './MaterialIcon'
import NoteTypeIcon from './NoteTypeIcon'
import SettingsIcon from './SettingsIcon'
import AuditLogIcon from './AuditLogIcon'
import DeveloperIcon from './DeveloperIcon'
import PersonIcon from './PersonIcon'
import HostIcon from './HostIcon'
import DocumentPrinterIcon from './DocumentPrinterIcon'
import DocumentTemplateIcon from './DocumentTemplateIcon'
import DocumentIcon from './DocumentIcon'
import DocumentSizeIcon from './DocumentSizeIcon'
import DocumentJobIcon from './DocumentJobIcon'
import FileIcon from './FileIcon'
import CourierIcon from './CourierIcon'
import CourierServiceIcon from './CourierServiceIcon'
import TaxRateIcon from './TaxRateIcon'
import TaxRecordIcon from './TaxRecordIcon'
import AppPasswordIcon from './AppPasswordIcon'
const toEmoji = (emoji) => <span aria-hidden>{emoji}</span>
const sidebarIconMap = {
production: <ProductionIcon />,
printer: <PrinterIcon />,
job: <JobIcon />,
subJob: <SubJobIcon />,
gcodeFile: <GCodeFileIcon />,
inventory: <InventoryIcon />,
filamentStock: <FilamentStockIcon />,
partStock: <PartStockIcon />,
productStock: <ProductStockIcon />,
stockEvent: <StockEventIcon />,
stockAudit: <StockAuditIcon />,
purchaseOrder: <PurchaseOrderIcon />,
orderItem: <OrderItemIcon />,
shipment: <ShipmentIcon />,
stockLocation: <StockLocationIcon />,
stockTransfer: <StockTransferIcon />,
sales: <SalesIcon />,
client: <ClientIcon />,
salesOrder: <SalesOrderIcon />,
marketplace: <MarketplaceIcon />,
listing: <ListingIcon />,
finance: <FinanceIcon />,
invoice: <InvoiceIcon />,
payment: <PaymentIcon />,
filament: <FilamentIcon />,
filamentSku: <FilamentSkuIcon />,
part: <PartIcon />,
partSku: <PartSkuIcon />,
product: <ProductIcon />,
productCategory: <ProductCategoryIcon />,
productSku: <ProductSkuIcon />,
vendor: <VendorIcon />,
material: <MaterialIcon />,
noteType: <NoteTypeIcon />,
settings: <SettingsIcon />,
auditLog: <AuditLogIcon />,
developer: <DeveloperIcon />,
person: <PersonIcon />,
host: <HostIcon />,
documentPrinter: <DocumentPrinterIcon />,
documentTemplate: <DocumentTemplateIcon />,
document: <DocumentIcon />,
documentSize: <DocumentSizeIcon />,
documentJob: <DocumentJobIcon />,
file: <FileIcon />,
courier: <CourierIcon />,
courierService: <CourierServiceIcon />,
taxRate: <TaxRateIcon />,
taxRecord: <TaxRecordIcon />,
appPassword: <AppPasswordIcon />,
sessionStorage: toEmoji('🗃️'),
authDebug: toEmoji('🔐'),
apiDebug: toEmoji('🌐')
}
export const getSidebarIconNode = (iconKey) => {
if (!iconKey) return null
return sidebarIconMap[iconKey] || null
}

160
src/database/Sidebars.js Normal file
View File

@ -0,0 +1,160 @@
import productionSidebarItems from './sidebars/production'
import inventorySidebarItems from './sidebars/inventory'
import salesSidebarItems from './sidebars/sales'
import financeSidebarItems from './sidebars/finance'
import managementSidebarItems from './sidebars/management'
import developerSidebarItems from './sidebars/developer'
const SIDEBARS = {
production: {
key: 'production',
label: 'Production',
iconKey: 'production',
items: productionSidebarItems
},
inventory: {
key: 'inventory',
label: 'Inventory',
iconKey: 'inventory',
items: inventorySidebarItems
},
sales: {
key: 'sales',
label: 'Sales',
iconKey: 'sales',
items: salesSidebarItems,
routeAliases: {
'/dashboard/sales/listingvarients': 'listings'
}
},
finance: {
key: 'finance',
label: 'Finance',
iconKey: 'finance',
items: financeSidebarItems
},
management: {
key: 'management',
label: 'Management',
iconKey: 'settings',
items: managementSidebarItems
},
developer: {
key: 'developer',
label: 'Developer',
iconKey: 'developer',
items: developerSidebarItems
}
}
const splitPath = (path) => path.split('/').filter(Boolean)
const isPathPrefixMatch = (candidatePath, pathname) => {
const candidateParts = splitPath(candidatePath)
const pathParts = splitPath(pathname)
if (candidateParts.length > pathParts.length) return false
for (let i = 0; i < candidateParts.length; i++) {
if (candidateParts[i] !== pathParts[i]) return false
}
return true
}
const getFilteredItems = (items = [], includeDev = false) =>
items
.filter((item) => item?.devOnly !== true || includeDev)
.map((item) => {
if (item?.children && Array.isArray(item.children)) {
return { ...item, children: getFilteredItems(item.children, includeDev) }
}
return { ...item }
})
const flattenPathEntries = (items = [], acc = []) => {
items.forEach((item) => {
if (item?.type === 'divider') return
if (item?.path) {
acc.push({ path: item.path, key: item.key })
}
if (item?.children && Array.isArray(item.children)) {
flattenPathEntries(item.children, acc)
}
})
return acc
}
const findDefaultKey = (items = []) => {
for (const item of items) {
if (item?.type === 'divider') continue
if (item?.children && item.children.length > 0) {
const childKey = findDefaultKey(item.children)
if (childKey) return childKey
}
if (item?.key) return item.key
}
return ''
}
const toMenuItems = (items = []) =>
items.map((item) => {
if (item?.type === 'divider') return { type: 'divider' }
const menuItem = {
key: item.key,
label: item.label,
iconKey: item.iconKey
}
if (item?.path) {
menuItem.path = item.path
}
if (item?.children && Array.isArray(item.children)) {
menuItem.children = toMenuItems(item.children)
}
return menuItem
})
export const getSidebar = (sidebarKey) => SIDEBARS[sidebarKey] || null
export const getSidebarItems = (sidebarKey, options = {}) => {
const sidebar = getSidebar(sidebarKey)
if (!sidebar) return []
const { includeDev = false } = options
return getFilteredItems(sidebar.items, includeDev)
}
export const getSidebarSelectedKey = (sidebarKey, pathname, options = {}) => {
const sidebar = getSidebar(sidebarKey)
if (!sidebar) return ''
const items = getSidebarItems(sidebarKey, options)
const pathEntries = flattenPathEntries(items)
const aliasEntries = Object.entries(sidebar.routeAliases || {}).map(
([path, key]) => ({ path, key })
)
const match = [...pathEntries, ...aliasEntries]
.filter((entry) => isPathPrefixMatch(entry.path, pathname))
.sort((a, b) => splitPath(b.path).length - splitPath(a.path).length)[0]
return match?.key || findDefaultKey(items)
}
export const getSidebarMenuSections = (options = {}) => {
const { includeDev = false } = options
return Object.values(SIDEBARS)
.filter((sidebar) => includeDev || sidebar.key !== 'developer')
.map((sidebar) => ({
key: sidebar.key,
label: sidebar.label,
iconKey: sidebar.iconKey,
items: toMenuItems(getSidebarItems(sidebar.key, { includeDev }))
}))
}
export const getSidebarDefaultPath = (sidebarKey, options = {}) => {
const items = getSidebarItems(sidebarKey, options)
const first = flattenPathEntries(items)[0]
return first?.path || '/dashboard/production/overview'
}

View File

@ -186,14 +186,13 @@ export const Filament = {
columnWidth: 150,
value: (objectData) => {
const cost = objectData?.cost
if (!cost) return undefined
if (!cost) return 0
if (objectData?.costTaxRate?.rateType == 'percentage') {
return (
(cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) ||
undefined
(cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) || 0
)
} else if (objectData?.costTaxRate?.rateType == 'amount') {
return (cost + objectData?.costTaxRate?.rate).toFixed(2) || undefined
return (cost + objectData?.costTaxRate?.rate).toFixed(2) || 0
}
return cost
}

View File

@ -201,11 +201,11 @@ export const FilamentSku = {
if (!objectData?.overrideCost) return undefined
const cost = objectData?.cost
const taxRate = objectData?.costTaxRate
if (!cost) return undefined
if (!cost) return 0
if (taxRate?.rateType == 'percentage') {
return (cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
return (cost * (1 + taxRate?.rate / 100)).toFixed(2) || 0
} else if (taxRate?.rateType == 'amount') {
return (cost + taxRate?.rate).toFixed(2) || undefined
return (cost + taxRate?.rate).toFixed(2) || 0
}
return cost
},

View File

@ -395,20 +395,19 @@ export const Invoice = {
columnWidth: 200,
value: (objectData) => {
const invoiceAmount = objectData?.invoiceAmount || 0
if (!invoiceAmount) return 0
if (objectData?.taxRate?.rateType == 'percentage') {
return (
(invoiceAmount * (1 + objectData?.taxRate?.rate / 100)).toFixed(
2
) || undefined
) || 0
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
(invoiceAmount + objectData?.taxRate?.rate).toFixed(2) ||
undefined
(invoiceAmount + objectData?.taxRate?.rate).toFixed(2) || 0
)
} else {
return invoiceAmount || 0
}
return invoiceAmount
}
}
],
@ -505,20 +504,19 @@ export const Invoice = {
columnWidth: 200,
value: (objectData) => {
const invoiceAmount = objectData?.invoiceAmount || 0
if (!invoiceAmount) return 0
if (objectData?.taxRate?.rateType == 'percentage') {
return (
(invoiceAmount * (1 + objectData?.taxRate?.rate / 100)).toFixed(
2
) || undefined
) || 0
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
(invoiceAmount + objectData?.taxRate?.rate).toFixed(2) ||
undefined
(invoiceAmount + objectData?.taxRate?.rate).toFixed(2) || 0
)
} else {
return invoiceAmount || 0
}
return invoiceAmount
}
}
],

View File

@ -370,24 +370,20 @@ export const OrderItem = {
columnWidth: 175,
value: (objectData) => {
const totalAmount = objectData?.itemAmount * objectData?.quantity || 0
if (!totalAmount) return 0
if (objectData?.taxRate?.rateType == 'percentage') {
if (objectData?.quantity == undefined || objectData?.quantity == 0) {
return undefined
}
return (
(
(totalAmount || 0) *
totalAmount *
(1 + objectData?.taxRate?.rate / 100)
).toFixed(2) || undefined
).toFixed(2) || 0
)
} else if (objectData?.taxRate?.rateType == 'amount') {
return (
((totalAmount || 0) + objectData?.taxRate?.rate).toFixed(2) ||
undefined
(totalAmount + objectData?.taxRate?.rate).toFixed(2) || 0
)
} else {
return totalAmount || 0
}
return totalAmount
}
},
{

View File

@ -155,14 +155,13 @@ export const Part = {
columnWidth: 150,
value: (objectData) => {
const cost = objectData?.cost
if (!cost) return undefined
if (!cost) return 0
if (objectData?.costTaxRate?.rateType == 'percentage') {
return (
(cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) ||
undefined
(cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) || 0
)
} else if (objectData?.costTaxRate?.rateType == 'amount') {
return (cost + objectData?.costTaxRate?.rate).toFixed(2) || undefined
return (cost + objectData?.costTaxRate?.rate).toFixed(2) || 0
}
return cost
}
@ -241,16 +240,13 @@ export const Part = {
} else {
price = objectData?.price
}
if (!price) return undefined
if (!price) return 0
if (objectData?.priceTaxRate?.rateType == 'percentage') {
return (
(price * (1 + objectData?.priceTaxRate?.rate / 100)).toFixed(2) ||
undefined
(price * (1 + objectData?.priceTaxRate?.rate / 100)).toFixed(2) || 0
)
} else if (objectData?.priceTaxRate?.rateType == 'amount') {
return (
(price + objectData?.priceTaxRate?.rate).toFixed(2) || undefined
)
return (price + objectData?.priceTaxRate?.rate).toFixed(2) || 0
}
return price
}

View File

@ -201,11 +201,11 @@ export const PartSku = {
if (!objectData?.overrideCost) return undefined
const cost = objectData?.cost
const taxRate = objectData?.costTaxRate
if (!cost) return undefined
if (!cost) return 0
if (taxRate?.rateType == 'percentage') {
return (cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
return (cost * (1 + taxRate?.rate / 100)).toFixed(2) || 0
} else if (taxRate?.rateType == 'amount') {
return (cost + taxRate?.rate).toFixed(2) || undefined
return (cost + taxRate?.rate).toFixed(2) || 0
}
return cost
},
@ -276,13 +276,13 @@ export const PartSku = {
} else {
price = objectData?.price
}
if (price == null) return undefined
if (!price) return 0
const taxRate =
objectData?.priceTaxRate ?? objectData?.part?.priceTaxRate
if (taxRate?.rateType == 'percentage') {
return (price * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
return (price * (1 + taxRate?.rate / 100)).toFixed(2) || 0
} else if (taxRate?.rateType == 'amount') {
return (price + taxRate?.rate).toFixed(2) || undefined
return (price + taxRate?.rate).toFixed(2) || 0
}
return price
},

View File

@ -212,11 +212,11 @@ export const ProductSku = {
if (!objectData?.overrideCost) return undefined
const cost = objectData?.cost
const taxRate = objectData?.costTaxRate
if (!cost) return undefined
if (!cost) return 0
if (taxRate?.rateType == 'percentage') {
return (cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
return (cost * (1 + taxRate?.rate / 100)).toFixed(2) || 0
} else if (taxRate?.rateType == 'amount') {
return (cost + taxRate?.rate).toFixed(2) || undefined
return (cost + taxRate?.rate).toFixed(2) || 0
}
return cost
},
@ -289,13 +289,13 @@ export const ProductSku = {
} else {
price = objectData?.price
}
if (price == null) return undefined
if (!price) return 0
const taxRate =
objectData?.priceTaxRate ?? objectData?.product?.priceTaxRate
if (taxRate?.rateType == 'percentage') {
return (price * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
return (price * (1 + taxRate?.rate / 100)).toFixed(2) || 0
} else if (taxRate?.rateType == 'amount') {
return (price + taxRate?.rate).toFixed(2) || undefined
return (price + taxRate?.rate).toFixed(2) || 0
}
return price
},

View File

@ -0,0 +1,22 @@
const developerSidebarItems = [
{
key: 'sessionstorage',
label: 'Session Storage',
iconKey: 'sessionStorage',
path: '/dashboard/developer/sessionstorage'
},
{
key: 'authcontextdebug',
label: 'Auth Debug',
iconKey: 'authDebug',
path: '/dashboard/developer/authcontextdebug'
},
{
key: 'apicontextdebug',
label: 'API Debug',
iconKey: 'apiDebug',
path: '/dashboard/developer/apicontextdebug'
}
]
export default developerSidebarItems

View File

@ -0,0 +1,23 @@
const financeSidebarItems = [
{
key: 'overview',
label: 'Overview',
iconKey: 'finance',
path: '/dashboard/finance/overview'
},
{ type: 'divider' },
{
key: 'invoices',
label: 'Invoices',
iconKey: 'invoice',
path: '/dashboard/finance/invoices'
},
{
key: 'payments',
label: 'Payments',
iconKey: 'payment',
path: '/dashboard/finance/payments'
}
]
export default financeSidebarItems

View File

@ -0,0 +1,74 @@
const inventorySidebarItems = [
{
key: 'overview',
label: 'Overview',
iconKey: 'inventory',
path: '/dashboard/inventory/overview'
},
{ type: 'divider' },
{
key: 'filamentstocks',
label: 'Filament Stocks',
iconKey: 'filamentStock',
path: '/dashboard/inventory/filamentstocks'
},
{
key: 'partstocks',
label: 'Part Stocks',
iconKey: 'partStock',
path: '/dashboard/inventory/partstocks'
},
{
key: 'productstocks',
label: 'Product Stocks',
iconKey: 'productStock',
path: '/dashboard/inventory/productstocks'
},
{ type: 'divider' },
{
key: 'purchaseorders',
label: 'Purchase Orders',
iconKey: 'purchaseOrder',
path: '/dashboard/inventory/purchaseorders'
},
{ type: 'divider' },
{
key: 'orderitems',
label: 'Order Items',
iconKey: 'orderItem',
path: '/dashboard/inventory/orderitems'
},
{
key: 'shipments',
label: 'Shipments',
iconKey: 'shipment',
path: '/dashboard/inventory/shipments'
},
{ type: 'divider' },
{
key: 'stocklocations',
label: 'Stock Locations',
iconKey: 'stockLocation',
path: '/dashboard/inventory/stocklocations'
},
{
key: 'stockevents',
label: 'Stock Events',
iconKey: 'stockEvent',
path: '/dashboard/inventory/stockevents'
},
{
key: 'stockaudits',
label: 'Stock Audits',
iconKey: 'stockAudit',
path: '/dashboard/inventory/stockaudits'
},
{
key: 'stocktransfers',
label: 'Stock Transfers',
iconKey: 'stockTransfer',
path: '/dashboard/inventory/stocktransfers'
}
]
export default inventorySidebarItems

View File

@ -0,0 +1,168 @@
const managementSidebarItems = [
{
key: 'filaments',
iconKey: 'filament',
label: 'Filaments',
path: '/dashboard/management/filaments'
},
{
key: 'filamentSkus',
iconKey: 'filamentSku',
label: 'Filament SKUs',
path: '/dashboard/management/filamentskus'
},
{
key: 'parts',
iconKey: 'part',
label: 'Parts',
path: '/dashboard/management/parts'
},
{
key: 'partSkus',
iconKey: 'partSku',
label: 'Part SKUs',
path: '/dashboard/management/partskus'
},
{
key: 'products',
iconKey: 'product',
label: 'Products',
path: '/dashboard/management/products'
},
{
key: 'productCategories',
iconKey: 'productCategory',
label: 'Product Categories',
path: '/dashboard/management/productcategories'
},
{
key: 'productSkus',
iconKey: 'productSku',
label: 'Product SKUs',
path: '/dashboard/management/productskus'
},
{
key: 'vendors',
iconKey: 'vendor',
label: 'Vendors',
path: '/dashboard/management/vendors'
},
{
key: 'materials',
iconKey: 'material',
label: 'Materials',
path: '/dashboard/management/materials'
},
{ type: 'divider' },
{
key: 'couriers',
iconKey: 'courier',
label: 'Couriers',
path: '/dashboard/management/couriers'
},
{
key: 'courierServices',
iconKey: 'courierService',
label: 'Courier Services',
path: '/dashboard/management/courierservices'
},
{ type: 'divider' },
{
key: 'taxRates',
iconKey: 'taxRate',
label: 'Tax Rates',
path: '/dashboard/management/taxrates'
},
{
key: 'taxRecords',
iconKey: 'taxRecord',
label: 'Tax Records',
path: '/dashboard/management/taxrecords'
},
{ type: 'divider' },
{
key: 'noteTypes',
iconKey: 'noteType',
label: 'Note Types',
path: '/dashboard/management/notetypes'
},
{
key: 'documents',
iconKey: 'document',
label: 'Documents',
children: [
{
key: 'documentPrinters',
iconKey: 'documentPrinter',
label: 'Document Printers',
path: '/dashboard/management/documentprinters'
},
{
key: 'documentJobs',
iconKey: 'documentJob',
label: 'Document Jobs',
path: '/dashboard/management/documentjobs'
},
{
key: 'documentTemplates',
iconKey: 'documentTemplate',
label: 'Document Templates',
path: '/dashboard/management/documenttemplates'
},
{
key: 'documentSizes',
iconKey: 'documentSize',
label: 'Document Sizes',
path: '/dashboard/management/documentsizes'
}
]
},
{ type: 'divider' },
{
key: 'hosts',
iconKey: 'host',
label: 'Hosts',
path: '/dashboard/management/hosts'
},
{ type: 'divider' },
{
key: 'users',
iconKey: 'person',
label: 'Users',
path: '/dashboard/management/users'
},
{
key: 'appPasswords',
iconKey: 'appPassword',
label: 'App Passwords',
path: '/dashboard/management/apppasswords'
},
{
key: 'settings',
iconKey: 'settings',
label: 'Settings',
path: '/dashboard/management/settings'
},
{
key: 'files',
iconKey: 'file',
label: 'Files',
path: '/dashboard/management/files'
},
{
key: 'auditLogs',
iconKey: 'auditLog',
label: 'Audit Logs',
path: '/dashboard/management/auditlogs'
},
{ type: 'divider' },
{
key: 'developer',
iconKey: 'developer',
label: 'Developer',
path: '/dashboard/developer/sessionstorage',
devOnly: true
}
]
export default managementSidebarItems

View File

@ -0,0 +1,35 @@
const productionSidebarItems = [
{
key: 'overview',
iconKey: 'production',
label: 'Overview',
path: '/dashboard/production/overview'
},
{ type: 'divider' },
{
key: 'printers',
iconKey: 'printer',
label: 'Printers',
path: '/dashboard/production/printers'
},
{
key: 'jobs',
iconKey: 'job',
label: 'Jobs',
path: '/dashboard/production/jobs'
},
{
key: 'subJobs',
iconKey: 'subJob',
label: 'Sub Jobs',
path: '/dashboard/production/subjobs'
},
{
key: 'gcodeFiles',
iconKey: 'gcodeFile',
label: 'GCode Files',
path: '/dashboard/production/gcodefiles'
}
]
export default productionSidebarItems

View File

@ -0,0 +1,35 @@
const salesSidebarItems = [
{
key: 'overview',
label: 'Overview',
iconKey: 'sales',
path: '/dashboard/sales/overview'
},
{ type: 'divider' },
{
key: 'clients',
label: 'Clients',
iconKey: 'client',
path: '/dashboard/sales/clients'
},
{
key: 'salesorders',
label: 'Sales Orders',
iconKey: 'salesOrder',
path: '/dashboard/sales/salesorders'
},
{
key: 'marketplaces',
label: 'Marketplaces',
iconKey: 'marketplace',
path: '/dashboard/sales/marketplaces'
},
{
key: 'listings',
label: 'Listings',
iconKey: 'listing',
path: '/dashboard/sales/listings'
}
]
export default salesSidebarItems