Enhance Dashboard Navigation with Object Navigation Hook

- Introduced `useObjectNavigationTabPage` hook to streamline navigation tab management for various dashboard components, improving consistency in object handling.
- Updated multiple components (DocumentTemplateDesign, EmailTemplateDesign, NoteInfo, GCodeFilePreview, ControlPrinter) to utilize the new hook for better object navigation and state management.
- Adjusted CSS styles for dashboard tabs to improve layout and responsiveness, enhancing user experience across devices.
This commit is contained in:
Tom Butcher 2026-09-17 21:39:04 +01:00
parent 2f9f42c9e2
commit 7fef25d911
10 changed files with 120 additions and 8 deletions

View File

@ -3371,7 +3371,7 @@ body.objectKanbanColumnResizing * {
}
.dashboard-tabs-label {
max-width: 200px;
max-width: 280px;
margin-top: -2px;
}

View File

@ -19,6 +19,7 @@ import { useDashboardObjectTools } from '../../context/DashboardObjectToolsConte
import ScrollBox from '../../common/ScrollBox.jsx'
import TemplateEditor from '../../common/TemplateEditor.jsx'
import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage'
const log = loglevel.getLogger('DocumentTemplateDesign')
log.setLevel(config.logLevel)
@ -30,6 +31,11 @@ const DocumentTemplateDesign = () => {
const documentTemplateId = new URLSearchParams(location.search).get(
'documentTemplateId'
)
useObjectNavigationTabPage({
modelName: 'documentTemplate',
pageName: 'design',
objectId: documentTemplateId
})
const [collapseState, updateCollapseState] = useCollapseState(
'DocumentTemplateDesign',
{

View File

@ -16,6 +16,7 @@ import ObjectActions from '../../common/ObjectActions'
import TemplateEditor from '../../common/TemplateEditor'
import ScrollBox from '../../common/ScrollBox'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage'
const EmailTemplateDesign = () => {
const location = useLocation()
@ -24,6 +25,11 @@ const EmailTemplateDesign = () => {
const emailTemplateId = new URLSearchParams(location.search).get(
'emailTemplateId'
)
useObjectNavigationTabPage({
modelName: 'emailTemplate',
pageName: 'design',
objectId: emailTemplateId
})
const [collapseState, updateCollapseState] = useCollapseState(
'EmailTemplateDesign',
{ preview: true, editor: true, notes: true }

View File

@ -21,7 +21,7 @@ import ActivityIndicator from '../../common/ActivityIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import { useNavigationTabPage } from '../../context/NavigationTabsContext'
import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
@ -33,11 +33,15 @@ const log = loglevel.getLogger('NoteInfo')
log.setLevel(config.logLevel)
const NoteInfo = () => {
useNavigationTabPage({ title: 'Info - Note', modelName: 'note' })
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const noteId = new URLSearchParams(location.search).get('noteId')
useObjectNavigationTabPage({
modelName: 'note',
pageName: 'info',
objectId: noteId
})
const [collapseState, updateCollapseState] = useCollapseState('NoteInfo', {
info: true,
content: true,

View File

@ -21,6 +21,7 @@ import MissingPlaceholder from '../../common/MissingPlaceholder.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage'
const log = loglevel.getLogger('GCodeFilePreview')
log.setLevel(config.logLevel)
@ -29,6 +30,11 @@ const GCodeFilePreview = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId')
useObjectNavigationTabPage({
modelName: 'gcodeFile',
pageName: 'preview',
objectId: gcodeFileId
})
const [collapseState, updateCollapseState] = useCollapseState(
'GCodeFilePreview',
{

View File

@ -37,6 +37,7 @@ import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButt
import PrinterControlButtons from '../../common/PrinterControlButtons.jsx'
import PrinterQueue from '../../common/PrinterQueue.jsx'
import ListIcon from '../../../Icons/ListIcon.jsx'
import { useObjectNavigationTabPage } from '../../hooks/useObjectNavigationTabPage'
const log = loglevel.getLogger('ControlPrinter')
log.setLevel(config.logLevel)
@ -47,6 +48,11 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
const actionHandlerRef = useRef(null)
const isMobile = useMediaQuery({ maxWidth: 768 })
const printerId = new URLSearchParams(location.search).get('printerId')
useObjectNavigationTabPage({
modelName: 'printer',
pageName: 'control',
objectId: printerId
})
const [collapseState, updateCollapseState] = useCollapseState(
'ControlPrinter',
{

View File

@ -16,7 +16,7 @@ const { Text } = Typography
const DashboardTabLabel = ({ tab, onClose }) => (
<Flex align='center' gap={6} className='dashboard-tabs-label'>
<Text ellipsis style={{ maxWidth: 160 }}>
<Text ellipsis style={{ maxWidth: 240 }}>
{tab.title || 'Farm Control'}
</Text>
<span

View File

@ -7,7 +7,8 @@ import { getModelByName } from '../../../database/ObjectModels'
import { getObjectIdFromSearch } from '../../../utils/modelActions'
import { useActions } from '../context/ActionsContext'
import { AuthContext } from '../context/AuthContext'
import { useNavigationTabPage, useIsNavigationTabActive } from '../context/NavigationTabsContext'
import { useIsNavigationTabActive } from '../context/NavigationTabsContext'
import { useObjectNavigationTabPage } from '../hooks/useObjectNavigationTabPage'
const ModelPage = ({ modelName, pageName }) => {
const model = getModelByName(modelName)
@ -16,9 +17,8 @@ const ModelPage = ({ modelName, pageName }) => {
const { setCurrentObject, setCurrentObjectType } = useActions()
const { userProfile } = useContext(AuthContext)
const objectId = getObjectIdFromSearch(modelName, location.search)
const pageTitle = `${pageName.charAt(0).toUpperCase()}${pageName.slice(1)} - ${model?.label || modelName}`
const isTabActive = useIsNavigationTabActive()
useNavigationTabPage({ title: pageTitle, modelName })
useObjectNavigationTabPage({ modelName, pageName, objectId })
useEffect(() => {
if (!isTabActive) return undefined

View File

@ -131,7 +131,9 @@ export const ObjectListViewProvider = ({ children, objectType, tableRef }) => {
const { getViewFromUrl, persistView, setObjectListView } = useTableState()
const listModel = getModelByName(objectType)
useNavigationTabPage({
title: listModel?.label ? `List - ${listModel.label}` : 'List',
title: listModel?.labelPlural
? `List - ${listModel.labelPlural}`
: 'List',
modelName: objectType
})

View File

@ -0,0 +1,82 @@
import { useContext, useEffect, useRef, useState } from 'react'
import { getModelByName } from '../../../database/ObjectModels'
import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext'
import { useNavigationTabPage } from '../context/NavigationTabsContext'
const formatPageLabel = (pageName) =>
pageName
? `${pageName.charAt(0).toUpperCase()}${pageName.slice(1)}`
: 'Page'
const getObjectDisplayName = (objectData, model) => {
if (objectData?.name) return objectData.name
if (objectData?._reference && model?.prefix) {
return `${model.prefix}:${objectData._reference}`
}
return null
}
export const formatObjectPageTabTitle = ({
pageName,
model,
objectName
} = {}) => {
const pageLabel = formatPageLabel(pageName)
const collectionLabel = model?.labelPlural
if (objectName && collectionLabel) {
return `${pageLabel} (${objectName}) - ${collectionLabel}`
}
if (objectName) return `${pageLabel} (${objectName})`
if (collectionLabel) return `${pageLabel} - ${collectionLabel}`
return pageLabel
}
export const useObjectNavigationTabPage = ({
modelName,
pageName,
objectId
} = {}) => {
const model = getModelByName(modelName)
const { fetchSpotlightData, subscribeToObjectUpdates, connected } =
useContext(ApiServerContext)
const { token } = useContext(AuthContext)
const [objectName, setObjectName] = useState(null)
const fetchSpotlightDataRef = useRef(fetchSpotlightData)
fetchSpotlightDataRef.current = fetchSpotlightData
useEffect(() => {
if (!objectId || !model?.prefix || token == null) {
setObjectName(null)
return undefined
}
let cancelled = false
const query = `${model.prefix}:${objectId}`
void fetchSpotlightDataRef.current(query).then((result) => {
if (cancelled) return
if (result && typeof result === 'object' && !Array.isArray(result)) {
setObjectName(getObjectDisplayName(result, model))
}
})
return () => {
cancelled = true
}
}, [model, objectId, token])
useEffect(() => {
if (!objectId || !modelName || !connected) return undefined
return subscribeToObjectUpdates(objectId, modelName, (value) => {
setObjectName((current) => getObjectDisplayName(value, model) || current)
})
}, [connected, model, modelName, objectId, subscribeToObjectUpdates])
useNavigationTabPage({
title: formatObjectPageTabTitle({ pageName, model, objectName }),
modelName
})
}
export default useObjectNavigationTabPage