Enhance Dashboard Functionality and Icon Integration
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Updated DashboardLayout to utilize Electron context for dynamic spacing adjustments. - Enhanced About and Settings components to include icon keys for improved navigation clarity. - Refined DashboardOverviewPage to generate dynamic icon keys based on page names. - Improved DashboardTabs to support sidebar icons, enhancing visual consistency. - Expanded NavigationTabsContext to manage icon keys for tabs, streamlining tab interactions. - Updated sidebarIconMap to utilize component references for better performance and maintainability.
This commit is contained in:
parent
2b0565e69e
commit
24a3a03c65
@ -1,6 +1,7 @@
|
|||||||
// DashboardLayout.js
|
// DashboardLayout.js
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Layout, Flex } from 'antd'
|
import { Layout, Flex } from 'antd'
|
||||||
|
import { useContext } from 'react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import ProductionSidebar from './Production/ProductionSidebar'
|
import ProductionSidebar from './Production/ProductionSidebar'
|
||||||
import InventorySidebar from './Inventory/InventorySidebar'
|
import InventorySidebar from './Inventory/InventorySidebar'
|
||||||
@ -16,12 +17,14 @@ import { useThemeContext } from './context/ThemeContext'
|
|||||||
import { MessageProvider } from './context/MessageContext'
|
import { MessageProvider } from './context/MessageContext'
|
||||||
import { useDashboardObjectToolsContext } from './context/DashboardObjectToolsContext'
|
import { useDashboardObjectToolsContext } from './context/DashboardObjectToolsContext'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import { ElectronContext } from './context/ElectronContext'
|
||||||
|
|
||||||
const { Content } = Layout
|
const { Content } = Layout
|
||||||
|
|
||||||
const DashboardLayout = ({ children }) => {
|
const DashboardLayout = ({ children }) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
|
const { isElectron } = useContext(ElectronContext)
|
||||||
const isProduction = location.pathname.startsWith('/dashboard/production')
|
const isProduction = location.pathname.startsWith('/dashboard/production')
|
||||||
const isInventory = location.pathname.startsWith('/dashboard/inventory')
|
const isInventory = location.pathname.startsWith('/dashboard/inventory')
|
||||||
const isFinance = location.pathname.startsWith('/dashboard/finance')
|
const isFinance = location.pathname.startsWith('/dashboard/finance')
|
||||||
@ -61,7 +64,11 @@ const DashboardLayout = ({ children }) => {
|
|||||||
className='main-content-layout'
|
className='main-content-layout'
|
||||||
>
|
>
|
||||||
<Content>
|
<Content>
|
||||||
<Flex vertical style={{ height: '100%' }} gap='10px'>
|
<Flex
|
||||||
|
vertical
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
gap={isElectron ? '10px' : '20px'}
|
||||||
|
>
|
||||||
<Flex justify='space-between' align='center'>
|
<Flex justify='space-between' align='center'>
|
||||||
<DashboardBreadcrumb style={{ margin: '16px 0' }} />
|
<DashboardBreadcrumb style={{ margin: '16px 0' }} />
|
||||||
{currentObjectTools}
|
{currentObjectTools}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { useNavigationTabPage } from '../context/NavigationTabsContext'
|
|||||||
const { Title, Text } = Typography
|
const { Title, Text } = Typography
|
||||||
|
|
||||||
const About = () => {
|
const About = () => {
|
||||||
useNavigationTabPage({ title: 'About' })
|
useNavigationTabPage({ title: 'About', iconKey: 'infoCircle' })
|
||||||
const [collapseState, updateCollapseState] = useCollapseState('About', {
|
const [collapseState, updateCollapseState] = useCollapseState('About', {
|
||||||
updater: true
|
updater: true
|
||||||
})
|
})
|
||||||
|
|||||||
@ -36,7 +36,7 @@ const LEGACY_ELECTRON_USER_KEYS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
useNavigationTabPage({ title: 'Settings' })
|
useNavigationTabPage({ title: 'Settings', iconKey: 'settings' })
|
||||||
const {
|
const {
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
isCompact,
|
isCompact,
|
||||||
|
|||||||
@ -125,10 +125,16 @@ const DashboardOverviewPage = ({
|
|||||||
onOpenActionsModal,
|
onOpenActionsModal,
|
||||||
sections = []
|
sections = []
|
||||||
}) => {
|
}) => {
|
||||||
const overviewTitle = pageName.endsWith('Overview')
|
const overviewSection = pageName.endsWith('Overview')
|
||||||
? `Overview - ${pageName.slice(0, -'Overview'.length)}`
|
? pageName.slice(0, -'Overview'.length)
|
||||||
|
: ''
|
||||||
|
const overviewTitle = overviewSection
|
||||||
|
? `Overview - ${overviewSection}`
|
||||||
: pageName
|
: pageName
|
||||||
useNavigationTabPage({ title: overviewTitle })
|
const overviewIconKey = overviewSection
|
||||||
|
? `${overviewSection.charAt(0).toLowerCase()}${overviewSection.slice(1)}`
|
||||||
|
: undefined
|
||||||
|
useNavigationTabPage({ title: overviewTitle, iconKey: overviewIconKey })
|
||||||
const [savedCollapseState, updateCollapseState] = useCollapseState(
|
const [savedCollapseState, updateCollapseState] = useCollapseState(
|
||||||
pageName,
|
pageName,
|
||||||
collapseDefaults
|
collapseDefaults
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import PlusIcon from '../../Icons/PlusIcon'
|
|||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import HomeIcon from '../../Icons/HomeIcon'
|
import HomeIcon from '../../Icons/HomeIcon'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import { getSidebarIconComponent } from '../../Icons/sidebarIconMap'
|
||||||
import { useNavigationTabs, useTabPreview } from '../context/NavigationTabsContext'
|
import { useNavigationTabs, useTabPreview } from '../context/NavigationTabsContext'
|
||||||
import { getDesktopWindowId } from '../../../electrobun-bridge.js'
|
import { getDesktopWindowId } from '../../../electrobun-bridge.js'
|
||||||
import { hasExternalTabDrag, writeTabDragData } from './tabDrag'
|
import { hasExternalTabDrag, writeTabDragData } from './tabDrag'
|
||||||
@ -88,7 +89,8 @@ const DashboardTabItem = ({
|
|||||||
const [previewOpen, setPreviewOpen] = useState(false)
|
const [previewOpen, setPreviewOpen] = useState(false)
|
||||||
const previewTargetRef = useRef(null)
|
const previewTargetRef = useRef(null)
|
||||||
const model = tab.modelName ? getModelByName(tab.modelName) : null
|
const model = tab.modelName ? getModelByName(tab.modelName) : null
|
||||||
const Icon = model?.icon || HomeIcon
|
const SidebarIcon = getSidebarIconComponent(tab.iconKey)
|
||||||
|
const Icon = model?.icon || SidebarIcon || HomeIcon
|
||||||
|
|
||||||
const handlePreviewOpenChange = useCallback(
|
const handlePreviewOpenChange = useCallback(
|
||||||
(nextOpen) => {
|
(nextOpen) => {
|
||||||
@ -178,7 +180,8 @@ DashboardTabItem.propTypes = {
|
|||||||
tab: PropTypes.shape({
|
tab: PropTypes.shape({
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
modelName: PropTypes.string
|
modelName: PropTypes.string,
|
||||||
|
iconKey: PropTypes.string
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
tabIndex: PropTypes.number.isRequired,
|
tabIndex: PropTypes.number.isRequired,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
|
|||||||
@ -72,6 +72,7 @@ const createTabFromLocation = (location, extras = {}) => ({
|
|||||||
id: createTabId(),
|
id: createTabId(),
|
||||||
title: extras.title || 'Farm Control',
|
title: extras.title || 'Farm Control',
|
||||||
modelName: extras.modelName || null,
|
modelName: extras.modelName || null,
|
||||||
|
iconKey: extras.iconKey || null,
|
||||||
history: [locationToEntry(location)],
|
history: [locationToEntry(location)],
|
||||||
historyIndex: 0
|
historyIndex: 0
|
||||||
})
|
})
|
||||||
@ -80,6 +81,7 @@ const cloneCurrentPageTab = (tab, location) => ({
|
|||||||
id: createTabId(),
|
id: createTabId(),
|
||||||
title: tab?.title || 'Farm Control',
|
title: tab?.title || 'Farm Control',
|
||||||
modelName: tab?.modelName || null,
|
modelName: tab?.modelName || null,
|
||||||
|
iconKey: tab?.iconKey || null,
|
||||||
history: [locationToEntry(location)],
|
history: [locationToEntry(location)],
|
||||||
historyIndex: 0
|
historyIndex: 0
|
||||||
})
|
})
|
||||||
@ -87,6 +89,73 @@ const cloneCurrentPageTab = (tab, location) => ({
|
|||||||
const getTabCurrentEntry = (tab) =>
|
const getTabCurrentEntry = (tab) =>
|
||||||
tab?.history?.[tab.historyIndex] || tab?.history?.[tab.history.length - 1]
|
tab?.history?.[tab.historyIndex] || tab?.history?.[tab.history.length - 1]
|
||||||
|
|
||||||
|
const normalizeHistory = (history, fallbackEntry) => {
|
||||||
|
if (Array.isArray(history) && history.length > 0) {
|
||||||
|
return history.map((item) => locationToEntry(item))
|
||||||
|
}
|
||||||
|
return [fallbackEntry || { pathname: '/', search: '', hash: '' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeTab = (tab, fallbackLocation) => {
|
||||||
|
const fallbackEntry = locationToEntry(fallbackLocation)
|
||||||
|
const history = normalizeHistory(tab?.history, fallbackEntry)
|
||||||
|
const rawIndex = Number.isInteger(tab?.historyIndex)
|
||||||
|
? tab.historyIndex
|
||||||
|
: history.length - 1
|
||||||
|
return {
|
||||||
|
id: tab?.id || createTabId(),
|
||||||
|
title: tab?.title || 'Farm Control',
|
||||||
|
modelName: tab?.modelName || null,
|
||||||
|
iconKey: tab?.iconKey || null,
|
||||||
|
history,
|
||||||
|
historyIndex: Math.min(Math.max(rawIndex, 0), history.length - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyLocationToTab = (tab, entry) => {
|
||||||
|
if (!tab || !entry) return tab
|
||||||
|
const currentEntry = getTabCurrentEntry(tab)
|
||||||
|
if (entriesEqual(currentEntry, entry)) return tab
|
||||||
|
if (!tab.history?.length) {
|
||||||
|
return { ...tab, history: [entry], historyIndex: 0 }
|
||||||
|
}
|
||||||
|
const truncated = tab.history.slice(0, tab.historyIndex + 1)
|
||||||
|
return {
|
||||||
|
...tab,
|
||||||
|
history: [...truncated, entry],
|
||||||
|
historyIndex: truncated.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyPageMetaToTab = (tab, { title, modelName, iconKey } = {}) => {
|
||||||
|
const nextModelName =
|
||||||
|
modelName === undefined
|
||||||
|
? iconKey !== undefined
|
||||||
|
? null
|
||||||
|
: tab.modelName
|
||||||
|
: modelName || null
|
||||||
|
const nextIconKey =
|
||||||
|
iconKey === undefined
|
||||||
|
? modelName !== undefined
|
||||||
|
? null
|
||||||
|
: tab.iconKey
|
||||||
|
: iconKey || null
|
||||||
|
const nextTitle = title || tab.title
|
||||||
|
if (
|
||||||
|
nextTitle === tab.title &&
|
||||||
|
nextModelName === tab.modelName &&
|
||||||
|
nextIconKey === tab.iconKey
|
||||||
|
) {
|
||||||
|
return tab
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...tab,
|
||||||
|
title: nextTitle,
|
||||||
|
modelName: nextModelName,
|
||||||
|
iconKey: nextIconKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const NavigationTabsProvider = ({ children }) => {
|
export const NavigationTabsProvider = ({ children }) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
@ -113,20 +182,53 @@ export const NavigationTabsProvider = ({ children }) => {
|
|||||||
const [tabPreviewCapturing, setTabPreviewCapturing] = useState({})
|
const [tabPreviewCapturing, setTabPreviewCapturing] = useState({})
|
||||||
const tabsRef = useRef(tabs)
|
const tabsRef = useRef(tabs)
|
||||||
const activeTabIdRef = useRef(activeTabId)
|
const activeTabIdRef = useRef(activeTabId)
|
||||||
|
const locationRef = useRef(location)
|
||||||
const previousActiveTabIdRef = useRef(null)
|
const previousActiveTabIdRef = useRef(null)
|
||||||
const isRestoringRef = useRef(false)
|
const isRestoringRef = useRef(null)
|
||||||
const tabPaneElsRef = useRef(new Map())
|
const tabPaneElsRef = useRef(new Map())
|
||||||
const captureQueueRef = useRef(Promise.resolve())
|
const captureQueueRef = useRef(Promise.resolve())
|
||||||
const captureInFlightRef = useRef(new Map())
|
const captureInFlightRef = useRef(new Map())
|
||||||
const previewThemeRef = useRef(isDarkMode ? 'dark' : 'light')
|
const previewThemeRef = useRef(isDarkMode ? 'dark' : 'light')
|
||||||
tabsRef.current = tabs
|
tabsRef.current = tabs
|
||||||
activeTabIdRef.current = activeTabId
|
activeTabIdRef.current = activeTabId
|
||||||
|
locationRef.current = location
|
||||||
previewThemeRef.current = isDarkMode ? 'dark' : 'light'
|
previewThemeRef.current = isDarkMode ? 'dark' : 'light'
|
||||||
|
|
||||||
|
const isRestoreLocation = useCallback((entry) => {
|
||||||
|
const restoring = isRestoringRef.current
|
||||||
|
if (!restoring) return false
|
||||||
|
if (restoring === true) return true
|
||||||
|
return (
|
||||||
|
entriesEqual(restoring.target, entry) ||
|
||||||
|
entriesEqual(restoring.from, entry)
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const shouldIgnoreRestoredLocation = useCallback((entry) => {
|
||||||
|
const restoring = isRestoringRef.current
|
||||||
|
if (!restoring) return false
|
||||||
|
if (restoring === true) {
|
||||||
|
isRestoringRef.current = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (entriesEqual(restoring.target, entry)) {
|
||||||
|
isRestoringRef.current = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (entriesEqual(restoring.from, entry)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
isRestoringRef.current = null
|
||||||
|
return false
|
||||||
|
}, [])
|
||||||
|
|
||||||
const restoreToEntry = useCallback(
|
const restoreToEntry = useCallback(
|
||||||
(entry) => {
|
(entry) => {
|
||||||
if (!entry) return
|
if (!entry) return
|
||||||
isRestoringRef.current = true
|
isRestoringRef.current = {
|
||||||
|
target: locationToEntry(entry),
|
||||||
|
from: locationToEntry(locationRef.current)
|
||||||
|
}
|
||||||
navigate(entryToPath(entry), { replace: true })
|
navigate(entryToPath(entry), { replace: true })
|
||||||
},
|
},
|
||||||
[navigate]
|
[navigate]
|
||||||
@ -141,13 +243,13 @@ export const NavigationTabsProvider = ({ children }) => {
|
|||||||
const nextEntry = getTabCurrentEntry(nextTab)
|
const nextEntry = getTabCurrentEntry(nextTab)
|
||||||
setActiveTabId(tabId)
|
setActiveTabId(tabId)
|
||||||
|
|
||||||
if (entriesEqual(nextEntry, location)) {
|
if (entriesEqual(nextEntry, locationRef.current)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreToEntry(nextEntry)
|
restoreToEntry(nextEntry)
|
||||||
},
|
},
|
||||||
[location, restoreToEntry]
|
[restoreToEntry]
|
||||||
)
|
)
|
||||||
|
|
||||||
const addTab = useCallback(() => {
|
const addTab = useCallback(() => {
|
||||||
@ -194,13 +296,13 @@ export const NavigationTabsProvider = ({ children }) => {
|
|||||||
if (nextActive) {
|
if (nextActive) {
|
||||||
setActiveTabId(nextActive.id)
|
setActiveTabId(nextActive.id)
|
||||||
const nextEntry = getTabCurrentEntry(nextActive)
|
const nextEntry = getTabCurrentEntry(nextActive)
|
||||||
if (!entriesEqual(nextEntry, location)) {
|
if (!entriesEqual(nextEntry, locationRef.current)) {
|
||||||
restoreToEntry(nextEntry)
|
restoreToEntry(nextEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
[handleWindowControl, location, restoreToEntry]
|
[handleWindowControl, restoreToEntry]
|
||||||
)
|
)
|
||||||
|
|
||||||
const closeTab = useCallback(
|
const closeTab = useCallback(
|
||||||
@ -213,29 +315,30 @@ export const NavigationTabsProvider = ({ children }) => {
|
|||||||
const acceptIncomingTab = useCallback(
|
const acceptIncomingTab = useCallback(
|
||||||
(incomingTab, targetId, insertBefore = false) => {
|
(incomingTab, targetId, insertBefore = false) => {
|
||||||
if (!incomingTab?.id) return
|
if (!incomingTab?.id) return
|
||||||
|
const tab = normalizeTab(incomingTab, locationRef.current)
|
||||||
|
|
||||||
setTabs((current) => {
|
setTabs((current) => {
|
||||||
if (current.some((tab) => tab.id === incomingTab.id)) {
|
if (current.some((item) => item.id === tab.id)) {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = [...current]
|
const next = [...current]
|
||||||
const targetIndex = next.findIndex((tab) => tab.id === targetId)
|
const targetIndex = next.findIndex((item) => item.id === targetId)
|
||||||
if (targetIndex === -1) {
|
if (targetIndex === -1) {
|
||||||
next.push(incomingTab)
|
next.push(tab)
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
next.splice(insertBefore ? targetIndex : targetIndex + 1, 0, incomingTab)
|
next.splice(insertBefore ? targetIndex : targetIndex + 1, 0, tab)
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
setActiveTabId(incomingTab.id)
|
setActiveTabId(tab.id)
|
||||||
const nextEntry = getTabCurrentEntry(incomingTab)
|
const nextEntry = getTabCurrentEntry(tab)
|
||||||
if (!entriesEqual(nextEntry, location)) {
|
if (!entriesEqual(nextEntry, locationRef.current)) {
|
||||||
restoreToEntry(nextEntry)
|
restoreToEntry(nextEntry)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[location, restoreToEntry]
|
[restoreToEntry]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleTabDragStart = useCallback(
|
const handleTabDragStart = useCallback(
|
||||||
@ -378,22 +481,31 @@ export const NavigationTabsProvider = ({ children }) => {
|
|||||||
void captureTabPreview(previousId)
|
void captureTabPreview(previousId)
|
||||||
}, [activeTabId, captureTabPreview, hydrated])
|
}, [activeTabId, captureTabPreview, hydrated])
|
||||||
|
|
||||||
const setTabPage = useCallback(({ title, modelName } = {}) => {
|
const setTabPage = useCallback(
|
||||||
|
({ title, modelName, iconKey, location: pageLocation } = {}) => {
|
||||||
const activeId = activeTabIdRef.current
|
const activeId = activeTabIdRef.current
|
||||||
if (!activeId) return
|
if (!activeId) return
|
||||||
|
|
||||||
setTabs((current) =>
|
const pageEntry = pageLocation ? locationToEntry(pageLocation) : null
|
||||||
current.map((tab) => {
|
|
||||||
|
setTabs((current) => {
|
||||||
|
if (current.length === 0) return current
|
||||||
|
|
||||||
|
let changed = false
|
||||||
|
const next = current.map((tab) => {
|
||||||
if (tab.id !== activeId) return tab
|
if (tab.id !== activeId) return tab
|
||||||
return {
|
let updated = applyPageMetaToTab(tab, { title, modelName, iconKey })
|
||||||
...tab,
|
if (pageEntry && !isRestoreLocation(pageEntry)) {
|
||||||
title: title || tab.title,
|
updated = applyLocationToTab(updated, pageEntry)
|
||||||
modelName:
|
|
||||||
modelName === undefined ? tab.modelName : modelName || null
|
|
||||||
}
|
}
|
||||||
|
if (updated !== tab) changed = true
|
||||||
|
return updated
|
||||||
})
|
})
|
||||||
|
return changed ? next : current
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[isRestoreLocation]
|
||||||
)
|
)
|
||||||
}, [])
|
|
||||||
|
|
||||||
const goBack = useCallback(() => {
|
const goBack = useCallback(() => {
|
||||||
const currentTab = tabsRef.current.find(
|
const currentTab = tabsRef.current.find(
|
||||||
@ -457,22 +569,24 @@ export const NavigationTabsProvider = ({ children }) => {
|
|||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
|
|
||||||
if (session?.tabs?.length) {
|
if (session?.tabs?.length) {
|
||||||
isRestoringRef.current = true
|
const restoredTabs = session.tabs.map((tab) =>
|
||||||
setTabs(session.tabs)
|
normalizeTab(tab, location)
|
||||||
|
)
|
||||||
|
setTabs(restoredTabs)
|
||||||
const nextActiveId =
|
const nextActiveId =
|
||||||
session.activeTabId &&
|
session.activeTabId &&
|
||||||
session.tabs.some((tab) => tab.id === session.activeTabId)
|
restoredTabs.some((tab) => tab.id === session.activeTabId)
|
||||||
? session.activeTabId
|
? session.activeTabId
|
||||||
: session.tabs[0].id
|
: restoredTabs[0].id
|
||||||
setActiveTabId(nextActiveId)
|
setActiveTabId(nextActiveId)
|
||||||
const activeTab =
|
const activeTab =
|
||||||
session.tabs.find((tab) => tab.id === nextActiveId) ||
|
restoredTabs.find((tab) => tab.id === nextActiveId) ||
|
||||||
session.tabs[0]
|
restoredTabs[0]
|
||||||
const entry = getTabCurrentEntry(activeTab)
|
const entry = getTabCurrentEntry(activeTab)
|
||||||
if (!entriesEqual(entry, location)) {
|
if (!entriesEqual(entry, location)) {
|
||||||
restoreToEntry(entry)
|
restoreToEntry(entry)
|
||||||
} else {
|
} else {
|
||||||
isRestoringRef.current = false
|
isRestoringRef.current = null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const initialTab = createTabFromLocation(location)
|
const initialTab = createTabFromLocation(location)
|
||||||
@ -493,12 +607,10 @@ export const NavigationTabsProvider = ({ children }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isElectron || !hydrated) return
|
if (!isElectron || !hydrated) return
|
||||||
if (isRestoringRef.current) {
|
|
||||||
isRestoringRef.current = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = locationToEntry(location)
|
const entry = locationToEntry(location)
|
||||||
|
if (shouldIgnoreRestoredLocation(entry)) return
|
||||||
|
|
||||||
setTabs((current) => {
|
setTabs((current) => {
|
||||||
if (current.length === 0) {
|
if (current.length === 0) {
|
||||||
const initialTab = createTabFromLocation(location)
|
const initialTab = createTabFromLocation(location)
|
||||||
@ -506,20 +618,16 @@ export const NavigationTabsProvider = ({ children }) => {
|
|||||||
return [initialTab]
|
return [initialTab]
|
||||||
}
|
}
|
||||||
|
|
||||||
return current.map((tab) => {
|
let changed = false
|
||||||
|
const next = current.map((tab) => {
|
||||||
if (tab.id !== activeTabIdRef.current) return tab
|
if (tab.id !== activeTabIdRef.current) return tab
|
||||||
const currentEntry = getTabCurrentEntry(tab)
|
const updated = applyLocationToTab(tab, entry)
|
||||||
if (entriesEqual(currentEntry, entry)) return tab
|
if (updated !== tab) changed = true
|
||||||
|
return updated
|
||||||
const truncated = tab.history.slice(0, tab.historyIndex + 1)
|
|
||||||
return {
|
|
||||||
...tab,
|
|
||||||
history: [...truncated, entry],
|
|
||||||
historyIndex: truncated.length
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
return changed ? next : current
|
||||||
})
|
})
|
||||||
}, [hydrated, isElectron, location])
|
}, [hydrated, isElectron, location, shouldIgnoreRestoredLocation])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isElectron || !hydrated || !syncWindowTabs) return undefined
|
if (!isElectron || !hydrated || !syncWindowTabs) return undefined
|
||||||
@ -670,21 +778,22 @@ export const useTabPreview = (tabId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export const useNavigationTabPage = ({ title, modelName } = {}) => {
|
export const useNavigationTabPage = ({ title, modelName, iconKey } = {}) => {
|
||||||
const { setTabPage, isElectron } = useContext(NavigationTabMetaContext)
|
const { setTabPage, isElectron } = useContext(NavigationTabMetaContext)
|
||||||
const store = useContext(NavigationTabActiveContext)
|
const store = useContext(NavigationTabActiveContext)
|
||||||
|
const pageLocation = useLocation()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isElectron || !title) return undefined
|
if (!isElectron || !title) return undefined
|
||||||
|
|
||||||
const sync = () => {
|
const sync = () => {
|
||||||
if (!store.getSnapshot()) return
|
if (!store.getSnapshot()) return
|
||||||
setTabPage({ title, modelName })
|
setTabPage({ title, modelName, iconKey, location: pageLocation })
|
||||||
}
|
}
|
||||||
|
|
||||||
sync()
|
sync()
|
||||||
return store.subscribe(sync)
|
return store.subscribe(sync)
|
||||||
}, [isElectron, modelName, setTabPage, store, title])
|
}, [iconKey, isElectron, modelName, pageLocation, setTabPage, store, title])
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
|||||||
@ -62,79 +62,91 @@ import MailIcon from './MailIcon'
|
|||||||
import EmailAccountIcon from './EmailAccountIcon'
|
import EmailAccountIcon from './EmailAccountIcon'
|
||||||
import EmailTemplateIcon from './EmailTemplateIcon'
|
import EmailTemplateIcon from './EmailTemplateIcon'
|
||||||
|
|
||||||
const toEmoji = (emoji) => <span aria-hidden>{emoji}</span>
|
const makeEmojiIcon = (emoji) => {
|
||||||
|
const EmojiIcon = (props) => (
|
||||||
|
<span aria-hidden {...props}>
|
||||||
|
{emoji}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
return EmojiIcon
|
||||||
|
}
|
||||||
|
|
||||||
const sidebarIconMap = {
|
const sidebarIconComponents = {
|
||||||
production: <ProductionIcon />,
|
production: ProductionIcon,
|
||||||
printer: <PrinterIcon />,
|
printer: PrinterIcon,
|
||||||
printerProfile: <PrinterProfileIcon />,
|
printerProfile: PrinterProfileIcon,
|
||||||
filamentProfile: <FilamentProfileIcon />,
|
filamentProfile: FilamentProfileIcon,
|
||||||
job: <JobIcon />,
|
job: JobIcon,
|
||||||
subJob: <SubJobIcon />,
|
subJob: SubJobIcon,
|
||||||
gcodeFile: <GCodeFileIcon />,
|
gcodeFile: GCodeFileIcon,
|
||||||
inventory: <InventoryIcon />,
|
inventory: InventoryIcon,
|
||||||
filamentStock: <FilamentStockIcon />,
|
filamentStock: FilamentStockIcon,
|
||||||
partStock: <PartStockIcon />,
|
partStock: PartStockIcon,
|
||||||
productStock: <ProductStockIcon />,
|
productStock: ProductStockIcon,
|
||||||
stockEvent: <StockEventIcon />,
|
stockEvent: StockEventIcon,
|
||||||
stockAudit: <StockAuditIcon />,
|
stockAudit: StockAuditIcon,
|
||||||
stockAuditLevel: <StockAuditLevelIcon />,
|
stockAuditLevel: StockAuditLevelIcon,
|
||||||
purchaseOrder: <PurchaseOrderIcon />,
|
purchaseOrder: PurchaseOrderIcon,
|
||||||
orderItem: <OrderItemIcon />,
|
orderItem: OrderItemIcon,
|
||||||
shipment: <ShipmentIcon />,
|
shipment: ShipmentIcon,
|
||||||
stockLocation: <StockLocationIcon />,
|
stockLocation: StockLocationIcon,
|
||||||
stockTransfer: <StockTransferIcon />,
|
stockTransfer: StockTransferIcon,
|
||||||
sales: <SalesIcon />,
|
sales: SalesIcon,
|
||||||
client: <ClientIcon />,
|
client: ClientIcon,
|
||||||
salesOrder: <SalesOrderIcon />,
|
salesOrder: SalesOrderIcon,
|
||||||
marketplace: <MarketplaceIcon />,
|
marketplace: MarketplaceIcon,
|
||||||
listing: <ListingIcon />,
|
listing: ListingIcon,
|
||||||
listingVarient: <ListingVarientIcon />,
|
listingVarient: ListingVarientIcon,
|
||||||
fulfillmentPolicy: <FulfillmentPolicyIcon />,
|
fulfillmentPolicy: FulfillmentPolicyIcon,
|
||||||
returnPolicy: <ReturnPolicyIcon />,
|
returnPolicy: ReturnPolicyIcon,
|
||||||
paymentPolicy: <PaymentPolicyIcon />,
|
paymentPolicy: PaymentPolicyIcon,
|
||||||
finance: <FinanceIcon />,
|
finance: FinanceIcon,
|
||||||
invoice: <InvoiceIcon />,
|
invoice: InvoiceIcon,
|
||||||
payment: <PaymentIcon />,
|
payment: PaymentIcon,
|
||||||
filament: <FilamentIcon />,
|
filament: FilamentIcon,
|
||||||
filamentSku: <FilamentSkuIcon />,
|
filamentSku: FilamentSkuIcon,
|
||||||
part: <PartIcon />,
|
part: PartIcon,
|
||||||
partSku: <PartSkuIcon />,
|
partSku: PartSkuIcon,
|
||||||
product: <ProductIcon />,
|
product: ProductIcon,
|
||||||
productCategory: <ProductCategoryIcon />,
|
productCategory: ProductCategoryIcon,
|
||||||
productSku: <ProductSkuIcon />,
|
productSku: ProductSkuIcon,
|
||||||
vendor: <VendorIcon />,
|
vendor: VendorIcon,
|
||||||
material: <MaterialIcon />,
|
material: MaterialIcon,
|
||||||
noteType: <NoteTypeIcon />,
|
noteType: NoteTypeIcon,
|
||||||
settings: <SettingsIcon />,
|
settings: SettingsIcon,
|
||||||
auditLog: <AuditLogIcon />,
|
auditLog: AuditLogIcon,
|
||||||
developer: <DeveloperIcon />,
|
developer: DeveloperIcon,
|
||||||
person: <PersonIcon />,
|
person: PersonIcon,
|
||||||
userGroup: <PersonGroupIcon />,
|
userGroup: PersonGroupIcon,
|
||||||
permissionSetting: <PermissionSettingIcon />,
|
permissionSetting: PermissionSettingIcon,
|
||||||
host: <HostIcon />,
|
host: HostIcon,
|
||||||
documentPrinter: <DocumentPrinterIcon />,
|
documentPrinter: DocumentPrinterIcon,
|
||||||
documentTemplate: <DocumentTemplateIcon />,
|
documentTemplate: DocumentTemplateIcon,
|
||||||
document: <DocumentIcon />,
|
document: DocumentIcon,
|
||||||
documentSize: <DocumentSizeIcon />,
|
documentSize: DocumentSizeIcon,
|
||||||
documentJob: <DocumentJobIcon />,
|
documentJob: DocumentJobIcon,
|
||||||
file: <FileIcon />,
|
file: FileIcon,
|
||||||
courier: <CourierIcon />,
|
courier: CourierIcon,
|
||||||
courierService: <CourierServiceIcon />,
|
courierService: CourierServiceIcon,
|
||||||
taxRate: <TaxRateIcon />,
|
taxRate: TaxRateIcon,
|
||||||
taxRecord: <TaxRecordIcon />,
|
taxRecord: TaxRecordIcon,
|
||||||
appPassword: <AppPasswordIcon />,
|
appPassword: AppPasswordIcon,
|
||||||
email: <MailIcon />,
|
email: MailIcon,
|
||||||
emailAccount: <EmailAccountIcon />,
|
emailAccount: EmailAccountIcon,
|
||||||
emailTemplate: <EmailTemplateIcon />,
|
emailTemplate: EmailTemplateIcon,
|
||||||
emailMessage: <MailIcon />,
|
emailMessage: MailIcon,
|
||||||
infoCircle: <InfoCircleIcon />,
|
infoCircle: InfoCircleIcon,
|
||||||
sessionStorage: toEmoji('🗃️'),
|
sessionStorage: makeEmojiIcon('🗃️'),
|
||||||
authDebug: toEmoji('🔐'),
|
authDebug: makeEmojiIcon('🔐'),
|
||||||
apiDebug: toEmoji('🌐')
|
apiDebug: makeEmojiIcon('🌐')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSidebarIconComponent = (iconKey) => {
|
||||||
|
if (!iconKey) return null
|
||||||
|
return sidebarIconComponents[iconKey] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSidebarIconNode = (iconKey) => {
|
export const getSidebarIconNode = (iconKey) => {
|
||||||
if (!iconKey) return null
|
const Icon = getSidebarIconComponent(iconKey)
|
||||||
return sidebarIconMap[iconKey] || null
|
return Icon ? <Icon /> : null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -471,6 +471,7 @@ function createDefaultTab() {
|
|||||||
id,
|
id,
|
||||||
title: 'Overview - Production',
|
title: 'Overview - Production',
|
||||||
modelName: null,
|
modelName: null,
|
||||||
|
iconKey: 'production',
|
||||||
history: [
|
history: [
|
||||||
{
|
{
|
||||||
pathname: DEFAULT_DASHBOARD_PATH,
|
pathname: DEFAULT_DASHBOARD_PATH,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user