// DashboardNavigation.js import { useContext, useEffect, useState, useMemo } from 'react' import { Menu, Flex, Tag, Space, Button, Badge, Divider, Typography, Popover } from 'antd' import { LoadingOutlined } from '@ant-design/icons' import { AuthContext } from '../context/AuthContext' import { SpotlightContext } from '../context/SpotlightContext' import { ApiServerContext } from '../context/ApiServerContext' import { NotificationContext } from '../context/NotificationContext' import Tooltip from './Tooltip' import { useNavigate, useLocation } from 'react-router-dom' import { Header } from 'antd/es/layout/layout' import { useMediaQuery } from 'react-responsive' import KeyboardShortcut from './KeyboardShortcut' import UserProfilePopover from './UserProfilePopover' 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' import WindowAppMenu from './WindowAppMenu' import WebAppSwitcher from './WebAppSwitcher' import DashboardTabs from './DashboardTabs' import { filterSidebarItemsByListPermission, getSidebarDefaultPath, getSidebarItems, getSidebarMenuSections, getSidebarSelectedKey, isSidebarSectionVisible } from '../../../database/Sidebars' import { getSidebarIconNode } from '../../Icons/sidebarIconMap' import { useAppUpdateContext } from '../context/AppUpdateContext' import { useThemeContext } from '../context/ThemeContext' import { getEffectiveAppearance } from '../../../database/Settings' const { Text } = Typography const mapSidebarItemsToMenu = (items, navigate) => items.map((item) => { if (item?.type === 'divider') { return item } const mappedItem = { key: item.key, icon: item.icon || getSidebarIconNode(item.iconKey), label: item.label } if (item.path) { mappedItem.onClick = () => navigate(item.path) } if (item?.children && Array.isArray(item.children)) { mappedItem.children = mapSidebarItemsToMenu(item.children, navigate) } return mappedItem }) const DashboardNavigation = () => { const { userProfile } = useContext(AuthContext) const { showSpotlight } = useContext(SpotlightContext) const { connecting, connected, userSettings, userSettingsLoaded } = useContext(ApiServerContext) const { authenticated } = useContext(AuthContext) const { showNavigationLabels, setShowNavigationLabels, setThemeMode, setDensityMode } = useThemeContext() const { toggleNotificationCenter, unreadCount } = useContext(NotificationContext) const [apiServerState, setApiServerState] = useState('disconnected') const navigate = useNavigate() const location = useLocation() const [selectedKey, setSelectedKey] = useState('production') const isMobile = useMediaQuery({ maxWidth: 768 }) const { platform, isElectron, getAppSettings, setSidebarViewMenu, isFullScreen, isMaximized } = useContext(ElectronContext) const { availableUpdate, checkForUpdates } = useAppUpdateContext() useEffect(() => { if (!userSettingsLoaded) return const hydrateAppearance = async () => { const electronSettings = isElectron ? await getAppSettings() : {} const effective = getEffectiveAppearance({ isElectron, userAppearance: userSettings?.appearance || {}, electronSettings }) if (effective.theme) setThemeMode(effective.theme) if (effective.density) setDensityMode(effective.density) if (effective.showNavigationLabels !== undefined) { setShowNavigationLabels(effective.showNavigationLabels) } } void hydrateAppearance() }, [ getAppSettings, isElectron, setDensityMode, setShowNavigationLabels, setThemeMode, userSettings?.appearance, userSettingsLoaded ]) const includeDev = import.meta.env.DEV const includeDevItems = import.meta.env.MODE === 'development' const mainMenuItems = useMemo(() => { const iconStyles = { fontSize: !showNavigationLabels ? '16px' : undefined, marginLeft: !showNavigationLabels ? '10px' : undefined } const navigationIcon = (icon, label) => showNavigationLabels ? ( icon ) : ( {icon} ) return [ { key: 'production', label: 'Production', className: 'electrobun-webkit-app-region-no-drag', icon: navigationIcon( , 'Production' ) }, { key: 'inventory', label: 'Inventory', className: 'electrobun-webkit-app-region-no-drag', icon: navigationIcon(, 'Inventory') }, { key: 'sales', label: 'Sales', className: 'electrobun-webkit-app-region-no-drag', icon: navigationIcon(, 'Sales') }, { key: 'finance', label: 'Finance', className: 'electrobun-webkit-app-region-no-drag', icon: navigationIcon(, 'Finance') }, { key: 'management', label: 'Management', className: 'electrobun-webkit-app-region-no-drag', icon: navigationIcon(, 'Management') } ].filter((item) => isSidebarSectionVisible(item.key, { includeDev, userProfile }) ) }, [includeDev, showNavigationLabels, userProfile]) const sidebarMenuItems = useMemo(() => { const items = filterSidebarItemsByListPermission( getSidebarItems(selectedKey, { includeDev: includeDevItems }), userProfile ) return mapSidebarItemsToMenu(items, navigate) }, [includeDevItems, navigate, selectedKey, userProfile]) const selectedSidebarItemKey = useMemo( () => getSidebarSelectedKey(selectedKey, location.pathname, { includeDev: includeDevItems, userProfile }), [includeDevItems, location.pathname, selectedKey, userProfile] ) const headerMenuItems = isMobile ? sidebarMenuItems : showNavigationLabels ? mainMenuItems : mainMenuItems.map((item) => ({ ...item, label: undefined })) const [userPopoverOpen, setUserPopoverOpen] = useState(false) const userPopoverContent = ( setUserPopoverOpen(false)} /> ) useEffect(() => { const pathParts = location.pathname.split('/').filter(Boolean) const sectionKey = pathParts[1] if (!sectionKey) return const nextItem = mainMenuItems.find((item) => item.key == sectionKey) setSelectedKey(nextItem?.key || sectionKey) }, [location.pathname, mainMenuItems]) useEffect(() => { if (connecting == true) { setApiServerState('connecting') } else if (connected == true) { setApiServerState('connected') } else { setApiServerState('disconnected') } }, [connecting, connected]) const handleMainMenuClick = ({ key }) => { navigate(getSidebarDefaultPath(key, { includeDev, userProfile })) } useEffect(() => { if (!isElectron || !setSidebarViewMenu) return const sections = getSidebarMenuSections({ includeDev, userProfile }) setSidebarViewMenu(sections) }, [includeDev, isElectron, setSidebarViewMenu, userProfile]) const isMacOSApp = isElectron && platform == 'darwin' const isOtherApp = isElectron && platform != 'darwin' const showDesktopLogo = !isElectron && !isMobile const showMobileLogo = !isElectron && isMobile const showControls = !isElectron || authenticated const menu = ( } style={{ marginBottom: '4px' }} /> } /> ) const navigationContents = ( {isMacOSApp ? : null} {isOtherApp ? ( <> {' '} > ) : null} {showControls && isMobile && menu} {showDesktopLogo == true ? ( ) : showMobileLogo == true ? ( ) : null} {showControls && !isMobile ? ( {menu} {isElectron ? ( <> > ) : null} ) : null} {!showControls && ( Farm Control )} {showControls && ( showSpotlight()} > } type='text' style={{ marginTop: '4px' }} onClick={() => showSpotlight()} /> toggleNotificationCenter()} > } type='text' style={{ marginTop: '2px' }} onClick={() => toggleNotificationCenter()} /> )} {import.meta.env.MODE === 'development' && ( {apiServerState === 'connected' ? ( } /> ) : null} {apiServerState === 'connecting' ? ( } /> ) : null} {apiServerState === 'disconnected' ? ( } /> ) : null} } onClick={() => { navigate('/dashboard/developer/sessionstorage') }} /> )} {showControls && userProfile ? ( }> {!isMobile && (userProfile?.name || userProfile.username)} ) : null} {showControls && isElectron && availableUpdate ? ( } style={{ cursor: 'pointer', margin: '2px 0 0 0' }} color='cyan' onClick={() => checkForUpdates()} > Update Available ) : null} {isOtherApp ? : null} ) return ( <> {isElectron ? ( {navigationContents} ) : ( {navigationContents} )} > ) } export default DashboardNavigation