diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css
index a46c1306..833e47f3 100644
--- a/assets/stylesheets/App.css
+++ b/assets/stylesheets/App.css
@@ -3356,3 +3356,96 @@ body.objectKanbanColumnResizing * {
.ant-steps-item-content {
min-height: 0;
}
+
+.mobile-footer-navigation.ant-layout-footer {
+ flex: 0 0 auto;
+ width: 100%;
+ line-height: normal;
+ padding: 8px 8px calc(8px + env(safe-area-inset-bottom, 0px));
+ background: var(--color-mobile-footer-bg);
+ color: var(--color-text);
+ border-top: 1px solid var(--color-mobile-footer-item-border);
+}
+
+.mobile-footer-navigation-tabs.segmented-nav {
+ display: flex;
+ width: 100%;
+}
+
+.mobile-footer-navigation .segmented-nav-group {
+ width: 100%;
+}
+
+.mobile-footer-navigation .segmented-nav-item {
+ flex: 1 1 0;
+ min-width: 0;
+ flex-direction: column;
+ gap: 10px;
+ padding: 12px;
+ border: none;
+ color: var(--color-text);
+ transition:
+ background 0.2s ease,
+ color 0.2s ease;
+}
+
+.mobile-footer-navigation .segmented-nav-item-icon {
+ padding-inline: 0;
+ min-width: 0;
+}
+
+.mobile-footer-navigation .segmented-nav-item-icon-contents {
+ min-height: 24px;
+ line-height: 24px;
+}
+
+.mobile-footer-navigation .segmented-nav-item-icon-contents .anticon {
+ font-size: 24px;
+}
+
+.mobile-footer-navigation .segmented-nav-thumb {
+ background: var(--color-mobile-footer-item-bg);
+ border: none;
+}
+
+.mobile-footer-navigation .segmented-nav-item-selected,
+.mobile-footer-navigation
+ .segmented-nav-item:not(.segmented-nav-item-disabled):hover {
+ background: var(--color-mobile-footer-item-bg);
+ border: none;
+}
+
+.mobile-footer-navigation .segmented-nav-item-selected {
+ color: var(--color-primary);
+}
+
+.mobile-footer-navigation .segmented-nav-item-selected .anticon,
+.mobile-footer-navigation .segmented-nav-item-selected svg {
+ color: inherit;
+}
+
+.mobile-footer-navigation .segmented-nav-item-label {
+ min-height: 0;
+ line-height: 1.2;
+ padding-inline: 0;
+ font-size: 11px;
+ font-weight: 500;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.main-layout.is-mobile {
+ overflow: hidden;
+}
+
+.mobile-menu.ant-menu-horizontal {
+ max-width: 32px;
+ margin-left: 4px;
+ margin-right: 12px;
+}
+
+.mobile-menu.ant-menu-horizontal .ant-menu-submenu {
+ padding: 0;
+}
diff --git a/src/components/Dashboard/Layout.jsx b/src/components/Dashboard/Layout.jsx
index adf0353d..4516ab36 100644
--- a/src/components/Dashboard/Layout.jsx
+++ b/src/components/Dashboard/Layout.jsx
@@ -11,14 +11,17 @@ import DashboardNavigation from './common/DashboardNavigation'
import DashboardBreadcrumb from './common/DashboardBreadcrumb'
import DeveloperSidebar from './Developer/DeveloperSidebar'
import DashboardSidebarSplitter from './common/DashboardSidebarSplitter'
+import MobileFooterNavigation from './common/MobileFooterNavigation'
import { useThemeContext } from './context/ThemeContext'
import { MessageProvider } from './context/MessageContext'
import { useDashboardObjectToolsContext } from './context/DashboardObjectToolsContext'
+import { useMediaQuery } from 'react-responsive'
const { Content } = Layout
const DashboardLayout = ({ children }) => {
const location = useLocation()
+ const isMobile = useMediaQuery({ maxWidth: 768 })
const isProduction = location.pathname.startsWith('/dashboard/production')
const isInventory = location.pathname.startsWith('/dashboard/inventory')
const isFinance = location.pathname.startsWith('/dashboard/finance')
@@ -49,7 +52,7 @@ const DashboardLayout = ({ children }) => {
@@ -68,6 +71,7 @@ const DashboardLayout = ({ children }) => {
+ {isMobile ? : null}
)
diff --git a/src/components/Dashboard/common/DashboardNavigation.jsx b/src/components/Dashboard/common/DashboardNavigation.jsx
index 5468ca07..a18dc49f 100644
--- a/src/components/Dashboard/common/DashboardNavigation.jsx
+++ b/src/components/Dashboard/common/DashboardNavigation.jsx
@@ -22,7 +22,6 @@ import { Header } from 'antd/es/layout/layout'
import { useMediaQuery } from 'react-responsive'
import KeyboardShortcut from './KeyboardShortcut'
import UserProfilePopover from './UserProfilePopover'
-import ElipsisText from './ElipsisText'
import FarmControlLogo from '../../Logos/FarmControlLogo'
import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall'
@@ -42,10 +41,14 @@ import DashboardWindowButtons from './DashboardWindowButtons'
import WindowAppMenu from './WindowAppMenu'
import WebAppSwitcher from './WebAppSwitcher'
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'
@@ -53,6 +56,29 @@ 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)
@@ -71,11 +97,6 @@ const DashboardNavigation = () => {
const navigate = useNavigate()
const location = useLocation()
const [selectedKey, setSelectedKey] = useState('production')
- const [selectedMenuItem, setSelectedMenuItem] = useState({
- key: 'production',
- label: 'Production',
- icon:
- })
const isMobile = useMediaQuery({ maxWidth: 768 })
const {
platform,
@@ -116,6 +137,7 @@ const DashboardNavigation = () => {
])
const includeDev = import.meta.env.DEV
+ const includeDevItems = import.meta.env.MODE === 'development'
const mainMenuItems = useMemo(() => {
const iconStyles = {
fontSize: !showNavigationLabels ? '16px' : undefined,
@@ -135,7 +157,10 @@ const DashboardNavigation = () => {
key: 'production',
label: 'Production',
className: 'electrobun-webkit-app-region-no-drag',
- icon: navigationIcon(, 'Production')
+ icon: navigationIcon(
+ ,
+ 'Production'
+ )
},
{
key: 'inventory',
@@ -166,6 +191,29 @@ const DashboardNavigation = () => {
)
}, [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 = (
@@ -174,15 +222,10 @@ const DashboardNavigation = () => {
useEffect(() => {
const pathParts = location.pathname.split('/').filter(Boolean)
- if (pathParts.length > 2) {
- const nextItem =
- mainMenuItems.find((item) => item.key == pathParts[1]) ||
- mainMenuItems[0]
- if (nextItem) {
- setSelectedMenuItem(nextItem)
- setSelectedKey(nextItem.key)
- }
- }
+ const sectionKey = pathParts[1]
+ if (!sectionKey) return
+ const nextItem = mainMenuItems.find((item) => item.key == sectionKey)
+ setSelectedKey(nextItem?.key || sectionKey)
}, [location.pathname, mainMenuItems])
useEffect(() => {
@@ -213,6 +256,29 @@ const DashboardNavigation = () => {
const showControls = !isElectron || authenticated
+ const menu = (
+
}
+ style={{ marginBottom: '4px' }}
+ />
+ }
+ />
+ )
+
const navigationContents = (
{isMacOSApp ? : null}
@@ -225,6 +291,7 @@ const DashboardNavigation = () => {
/>
>
) : null}
+ {showControls && isMobile && menu}
{showDesktopLogo == true ? (
{
}}
/>
) : null}
- {isMobile && (
-
- {selectedMenuItem?.icon}
-
- {selectedMenuItem?.label}
-
-
- )}
- {showControls && (
-
- {isMobile && }
{
align='center'
className='ant-menu-light'
style={{
- padding: '0 26px',
+ padding: isMobile ? '0 12px' : '0 26px',
height: '100%',
borderBottom: '1px solid rgba(5, 5, 5, 0.00)'
}}
diff --git a/src/components/Dashboard/common/DashboardSidebar.jsx b/src/components/Dashboard/common/DashboardSidebar.jsx
index 46ed81a5..5d564afe 100644
--- a/src/components/Dashboard/common/DashboardSidebar.jsx
+++ b/src/components/Dashboard/common/DashboardSidebar.jsx
@@ -6,7 +6,6 @@ import {
useContext
} from 'react'
import { Layout, Menu, Flex, Button, Divider } from 'antd'
-import { CaretDownFilled } from '@ant-design/icons'
import CollapseSidebarIcon from '../../Icons/CollapseSidebarIcon'
import ExpandSidebarIcon from '../../Icons/ExpandSidebarIcon'
import { useMediaQuery } from 'react-responsive'
@@ -82,8 +81,6 @@ const DashboardSidebar = ({
return item
}
- console.log(item)
-
const icon = item.icon || getSidebarIconNode(item.iconKey)
const mappedItem = {
key: item.key,
@@ -114,16 +111,7 @@ const DashboardSidebar = ({
const _items = mapItemsRecursively(allowedItems)
if (isMobile) {
- return (
- } />}
- />
- )
+ return null
}
return (
diff --git a/src/components/Dashboard/common/DashboardSidebarSplitter.jsx b/src/components/Dashboard/common/DashboardSidebarSplitter.jsx
index 8dae4aa6..0892e133 100644
--- a/src/components/Dashboard/common/DashboardSidebarSplitter.jsx
+++ b/src/components/Dashboard/common/DashboardSidebarSplitter.jsx
@@ -158,8 +158,7 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
if (isMobile) {
return (
-
- {sidebarNode}
+
{children}
)
diff --git a/src/components/Dashboard/common/MobileFooterNavigation.jsx b/src/components/Dashboard/common/MobileFooterNavigation.jsx
new file mode 100644
index 00000000..6c995fc3
--- /dev/null
+++ b/src/components/Dashboard/common/MobileFooterNavigation.jsx
@@ -0,0 +1,98 @@
+import { useContext, useMemo } from 'react'
+import { Layout } from 'antd'
+import { useMediaQuery } from 'react-responsive'
+import { useLocation, useNavigate } from 'react-router-dom'
+import { AuthContext } from '../context/AuthContext'
+import { ElectronContext } from '../context/ElectronContext'
+import {
+ getSidebarDefaultPath,
+ isSidebarSectionVisible
+} from '../../../database/Sidebars'
+import { getSidebarIconNode } from '../../Icons/sidebarIconMap'
+import SegmentedNav from './SegmentedNav'
+
+const { Footer } = Layout
+
+const MobileFooterNavigation = () => {
+ const isMobile = useMediaQuery({ maxWidth: 768 })
+ const { userProfile, authenticated } = useContext(AuthContext)
+ const { isElectron } = useContext(ElectronContext)
+ const navigate = useNavigate()
+ const location = useLocation()
+ const includeDev = import.meta.env.DEV
+
+ const sectionItems = useMemo(
+ () =>
+ [
+ {
+ key: 'production',
+ label: 'Production',
+ iconKey: 'production'
+ },
+ {
+ key: 'inventory',
+ label: 'Inventory',
+ iconKey: 'inventory'
+ },
+ {
+ key: 'sales',
+ label: 'Sales',
+ iconKey: 'sales'
+ },
+ {
+ key: 'finance',
+ label: 'Finance',
+ iconKey: 'finance'
+ },
+ {
+ key: 'management',
+ label: 'Management',
+ iconKey: 'settings'
+ }
+ ].filter((item) =>
+ isSidebarSectionVisible(item.key, { includeDev, userProfile })
+ ),
+ [includeDev, userProfile]
+ )
+
+ const selectedKey = useMemo(() => {
+ const pathParts = location.pathname.split('/').filter(Boolean)
+ const sectionKey = pathParts[1]
+ return (
+ sectionItems.find((item) => item.key === sectionKey)?.key ||
+ sectionItems[0]?.key
+ )
+ }, [location.pathname, sectionItems])
+
+ const options = useMemo(
+ () =>
+ sectionItems.map((item) => ({
+ value: item.key,
+ label: item.label,
+ icon: getSidebarIconNode(item.iconKey)
+ })),
+ [sectionItems]
+ )
+
+ const showControls = !isElectron || authenticated
+
+ if (!isMobile || !showControls || options.length === 0) {
+ return null
+ }
+
+ return (
+
+ )
+}
+
+export default MobileFooterNavigation
diff --git a/src/components/Dashboard/common/ObjectTable.jsx b/src/components/Dashboard/common/ObjectTable.jsx
index 9159fe8a..108a3555 100644
--- a/src/components/Dashboard/common/ObjectTable.jsx
+++ b/src/components/Dashboard/common/ObjectTable.jsx
@@ -388,7 +388,7 @@ const ObjectTable = forwardRef(
const { getViewFromUrl } = useTableState()
var adjustedScrollHeight = scrollHeight
if (isMobile) {
- adjustedScrollHeight = 'calc(var(--unit-100vh) - 298px)'
+ adjustedScrollHeight = 'calc(var(--unit-100vh) - 348px)'
}
if (isCards || isKanban || isTimeline) {
adjustedScrollHeight = 'calc(var(--unit-100vh) - 210px)'
diff --git a/src/components/Dashboard/context/ThemeContext.jsx b/src/components/Dashboard/context/ThemeContext.jsx
index e8fea3b9..24714d87 100644
--- a/src/components/Dashboard/context/ThemeContext.jsx
+++ b/src/components/Dashboard/context/ThemeContext.jsx
@@ -204,6 +204,18 @@ export const ThemeProvider = ({ children }) => {
'--color-text-placeholder',
isDarkMode ? '#4F4F4F' : '#BFBFBF'
)
+ root.style.setProperty(
+ '--color-mobile-footer-bg',
+ isDarkMode ? '#141414' : '#ffffff'
+ )
+ root.style.setProperty(
+ '--color-mobile-footer-item-bg',
+ isDarkMode ? '#1d1d1d' : '#f5f5f5'
+ )
+ root.style.setProperty(
+ '--color-mobile-footer-item-border',
+ isDarkMode ? '#303030' : '#f0f0f0'
+ )
}, [isDarkMode, primaryColorOverride])
const themeConfig = {