farmcontrol-ui/src/components/Dashboard/common/DashboardNavigation.jsx
Tom Butcher ca7ab55d1e Add sales module with client and sales order management features
- Introduced new SVG icons for client and sales order.
- Implemented SalesRoutes for navigation.
- Created components for managing clients and sales orders, including overview, client info, and order details.
- Added functionality for creating, editing, and canceling sales orders.
- Integrated sales statistics and actions within the dashboard layout.
2025-12-27 20:46:45 +00:00

372 lines
11 KiB
JavaScript

// DashboardNavigation.js
import { useContext, useEffect, useState, useMemo } from 'react'
import {
Menu,
Flex,
Tag,
Space,
Dropdown,
Button,
Tooltip,
Badge,
Divider,
Typography
} from 'antd'
import {
LogoutOutlined,
MailOutlined,
LoadingOutlined
} from '@ant-design/icons'
import { AuthContext } from '../context/AuthContext'
import { SpotlightContext } from '../context/SpotlightContext'
import { ApiServerContext } from '../context/ApiServerContext'
import { useNavigate, useLocation } from 'react-router-dom'
import { Header } from 'antd/es/layout/layout'
import { useMediaQuery } from 'react-responsive'
import KeyboardShortcut from './KeyboardShortcut'
import FarmControlLogo from '../../Logos/FarmControlLogo'
import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall'
import MenuIcon from '../../Icons/MenuIcon'
import ProductionIcon from '../../Icons/ProductionIcon'
import InventoryIcon from '../../Icons/InventoryIcon'
import FinanceIcon from '../../Icons/FinanceIcon'
import SalesIcon from '../../Icons/SalesIcon'
import PersonIcon from '../../Icons/PersonIcon'
import CloudIcon from '../../Icons/CloudIcon'
import BellIcon from '../../Icons/BellIcon'
import SearchIcon from '../../Icons/SearchIcon'
import SettingsIcon from '../../Icons/SettingsIcon'
import DeveloperIcon from '../../Icons/DeveloperIcon'
import { ElectronContext } from '../context/ElectronContext'
import DashboardWindowButtons from './DashboardWindowButtons'
const { Text } = Typography
const DashboardNavigation = () => {
const { logout, userProfile } = useContext(AuthContext)
const { showSpotlight } = useContext(SpotlightContext)
const { toggleNotificationCenter, unreadCount } = useContext(ApiServerContext)
const { connecting, connected } = useContext(ApiServerContext)
const [apiServerState, setApiServerState] = useState('disconnected')
const navigate = useNavigate()
const location = useLocation()
const [selectedKey, setSelectedKey] = useState('production')
const [selectedMenuItem, setSelectedMenuItem] = useState({
key: 'production',
label: 'Production',
icon: <ProductionIcon />
})
const isMobile = useMediaQuery({ maxWidth: 768 })
const { platform, isElectron, isFullScreen } = useContext(ElectronContext)
const mainMenuItems = useMemo(
() => [
{
key: 'production',
label: 'Production',
icon: <ProductionIcon />
},
{
key: 'inventory',
label: 'Inventory',
icon: <InventoryIcon />
},
{
key: 'sales',
label: 'Sales',
icon: <SalesIcon />
},
{
key: 'finance',
label: 'Finance',
icon: <FinanceIcon />
},
{
key: 'management',
label: 'Management',
icon: <SettingsIcon />
}
],
[]
)
const userMenuItems = {
items: [
{
key: 'username',
label: userProfile?.username,
icon: <PersonIcon />,
disabled: true
},
{
key: 'email',
label: userProfile?.email,
icon: <MailOutlined />,
disabled: true
},
{
key: 'logout',
label: 'Logout',
icon: <LogoutOutlined />
}
],
onClick: (key) => {
if (key === 'profile') {
navigate('/profile')
} else if (key === 'logout') {
logout()
}
}
}
useEffect(() => {
const pathParts = location.pathname.split('/').filter(Boolean)
if (pathParts.length > 2) {
setSelectedMenuItem(
mainMenuItems.filter((item) => item.key == pathParts[1])[0]
)
setSelectedKey(pathParts[1]) // Return the section (production/management)
}
}, [location.pathname, mainMenuItems])
useEffect(() => {
if (connecting == true) {
setApiServerState('connecting')
} else if (connected == true) {
setApiServerState('connected')
} else {
setApiServerState('disconnected')
}
}, [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')
}
}
const showAppLogo =
(isElectron && platform == 'darwin' && isFullScreen == true) ||
(isElectron && platform != 'darwin')
const isMacOSApp = isElectron && platform == 'darwin'
const isOtherApp = isElectron && platform != 'darwin'
const showDesktopLogo = !isElectron && !isMobile
const showMobileLogo = !isElectron && isMobile
const navigationContents = (
<Flex style={{ width: '100%' }} align='center'>
{isMacOSApp ? <DashboardWindowButtons /> : null}
{showAppLogo == true && (
<FarmControlLogoSmall
style={{
fontSize: '46px',
height: '16px',
marginLeft: '15px',
marginRight: '8px'
}}
/>
)}
{showDesktopLogo == true ? (
<FarmControlLogo
style={{
fontSize: '200px',
height: '18px',
marginRight: '15px'
}}
/>
) : showMobileLogo == true ? (
<FarmControlLogoSmall
style={{
fontSize: '48px',
marginRight: isElectron ? '20px' : '25px'
}}
/>
) : null}
{isMobile && (
<Flex
gap={'small'}
align='center'
style={{
marginLeft: '16px',
minWidth: 0, // allow children to shrink
maxWidth: '100%' // responsive
}}
>
{selectedMenuItem.icon}
<Text
ellipsis
style={{
minWidth: 0, // CRUCIAL for flex children
maxWidth: '100%', // Responsive
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
flex: 1 // Take available space, shrink as needed
}}
>
{selectedMenuItem.label}
</Text>
</Flex>
)}
<Menu
mode='horizontal'
className={isElectron ? 'electron-navigation' : null}
items={mainMenuItems}
style={{
flexWrap: 'wrap',
flexGrow: isMobile ? 0 : 1,
border: 0,
width: isMobile ? '64px' : 'unset'
}}
onClick={handleMainMenuClick}
selectedKeys={[selectedKey]}
overflowedIndicator={
<Button
type='text'
icon={<MenuIcon />}
style={{ marginBottom: '4px' }}
/>
}
/>
{isMobile && <div style={{ flexGrow: 1 }} />}
<Flex
gap={'small'}
align='center'
style={{ marginTop: '-2px', marginRight: '6px' }}
>
<Space>
<KeyboardShortcut
shortcut='alt+q'
hint='ALT Q'
onTrigger={() => showSpotlight()}
>
<Button
icon={<SearchIcon />}
type='text'
style={{ marginTop: '2px' }}
onClick={() => showSpotlight()}
/>
</KeyboardShortcut>
<Badge count={unreadCount} size='small'>
<KeyboardShortcut
shortcut='alt+n'
hint='ALT N'
onTrigger={() => toggleNotificationCenter()}
>
<Button
icon={<BellIcon />}
type='text'
style={{ marginTop: '2px' }}
onClick={() => toggleNotificationCenter()}
/>
</KeyboardShortcut>
</Badge>
</Space>
{import.meta.env.MODE === 'development' && (
<Space>
{apiServerState === 'connected' ? (
<Tooltip title='Connected to api server' arrow={false}>
<Tag
color='success'
style={{ marginRight: 0 }}
icon={<CloudIcon />}
/>
</Tooltip>
) : null}
{apiServerState === 'connecting' ? (
<Tooltip title='Connecting to api erver...' arrow={false}>
<Tag
color='warning'
style={{ marginRight: 0 }}
icon={<LoadingOutlined />}
/>
</Tooltip>
) : null}
{apiServerState === 'disconnected' ? (
<Tooltip title='Disconnected from api server' arrow={false}>
<Tag
color='error'
style={{ marginRight: 0 }}
icon={<CloudIcon />}
/>
</Tooltip>
) : null}
<Tooltip title='Developer' arrow={false}>
<Tag
color='yellow'
style={{ marginRight: 0 }}
icon={<DeveloperIcon />}
onClick={() => {
navigate('/dashboard/developer/sessionstorage')
}}
/>
</Tooltip>
</Space>
)}
{userProfile ? (
<Space>
<Dropdown menu={userMenuItems} placement='bottomRight'>
<Tag style={{ marginRight: 0 }} icon={<PersonIcon />}>
{!isMobile && (userProfile?.name || userProfile.username)}
</Tag>
</Dropdown>
</Space>
) : null}
</Flex>
{isOtherApp ? <DashboardWindowButtons /> : null}
</Flex>
)
return (
<>
{isElectron ? (
<Flex
className='ant-menu-horizontal ant-menu-light electron-navigation-wrapper'
style={{ lineHeight: '40px', padding: '0 2px 0 2px' }}
>
{navigationContents}
</Flex>
) : (
<Flex vertical>
<Header
style={{
width: '100vw',
padding: 0,
marginBottom: '0.1px',
background: 'unset'
}}
theme='light'
className='ant-menu-horizontal'
>
<Flex
gap={'large'}
align='center'
className='ant-menu-light'
style={{
padding: '0 26px',
height: '100%',
borderBottom: '1px solid rgba(5, 5, 5, 0.00)'
}}
>
{navigationContents}
</Flex>
<Divider style={{ margin: 0 }} />
</Header>
</Flex>
)}
</>
)
}
export default DashboardNavigation