Add ModelRoutes and ActionsContext for enhanced model management
- Introduced ModelRoutes to handle dynamic routing for model pages based on object models. - Created ActionsContext to manage actions and modal states across components, improving action handling and navigation. - Updated various components to utilize the new buildActionUrl utility for consistent URL generation. - Refactored existing components to integrate the ActionsProvider, enhancing state management and user interaction.
This commit is contained in:
parent
c3cfd0a55e
commit
89b6f476c1
11
src/App.jsx
11
src/App.jsx
@ -18,6 +18,7 @@ import {
|
|||||||
ElectronSpotlightContentPage
|
ElectronSpotlightContentPage
|
||||||
} from './components/Dashboard/context/SpotlightContext.jsx'
|
} from './components/Dashboard/context/SpotlightContext.jsx'
|
||||||
import { ActionsModalProvider } from './components/Dashboard/context/ActionsModalContext.jsx'
|
import { ActionsModalProvider } from './components/Dashboard/context/ActionsModalContext.jsx'
|
||||||
|
import { ActionsProvider } from './components/Dashboard/context/ActionsContext.jsx'
|
||||||
import MissingPlaceholder from './components/Dashboard/common/MissingPlaceholder.jsx'
|
import MissingPlaceholder from './components/Dashboard/common/MissingPlaceholder.jsx'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -46,7 +47,8 @@ import {
|
|||||||
FinanceRoutes,
|
FinanceRoutes,
|
||||||
SalesRoutes,
|
SalesRoutes,
|
||||||
ManagementRoutes,
|
ManagementRoutes,
|
||||||
DeveloperRoutes
|
DeveloperRoutes,
|
||||||
|
ModelRoutes
|
||||||
} from './routes'
|
} from './routes'
|
||||||
|
|
||||||
const getRouter = () => {
|
const getRouter = () => {
|
||||||
@ -83,11 +85,12 @@ const AppContent = () => {
|
|||||||
<PrintServerProvider>
|
<PrintServerProvider>
|
||||||
<ApiServerProvider>
|
<ApiServerProvider>
|
||||||
<MessageProvider>
|
<MessageProvider>
|
||||||
|
<TableStateProvider>
|
||||||
<AppUpdateProvider>
|
<AppUpdateProvider>
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<SpotlightProvider>
|
<SpotlightProvider>
|
||||||
<ActionsModalProvider>
|
<ActionsModalProvider>
|
||||||
<TableStateProvider>
|
<ActionsProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path='/applaunch'
|
path='/applaunch'
|
||||||
@ -146,6 +149,7 @@ const AppContent = () => {
|
|||||||
{FinanceRoutes}
|
{FinanceRoutes}
|
||||||
{SalesRoutes}
|
{SalesRoutes}
|
||||||
{ManagementRoutes}
|
{ManagementRoutes}
|
||||||
|
{ModelRoutes}
|
||||||
{DeveloperRoutes}
|
{DeveloperRoutes}
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
@ -158,11 +162,12 @@ const AppContent = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</TableStateProvider>
|
</ActionsProvider>
|
||||||
</ActionsModalProvider>
|
</ActionsModalProvider>
|
||||||
</SpotlightProvider>
|
</SpotlightProvider>
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</AppUpdateProvider>
|
</AppUpdateProvider>
|
||||||
|
</TableStateProvider>
|
||||||
</MessageProvider>
|
</MessageProvider>
|
||||||
</ApiServerProvider>
|
</ApiServerProvider>
|
||||||
</PrintServerProvider>
|
</PrintServerProvider>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import SpotlightTooltip from './SpotlightTooltip'
|
|||||||
import Thumbnail from './Thumbnail'
|
import Thumbnail from './Thumbnail'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import { buildActionUrl } from '../../../utils/modelActions'
|
||||||
|
|
||||||
const getUserId = (user) => user?._id || user
|
const getUserId = (user) => user?._id || user
|
||||||
|
|
||||||
@ -90,7 +91,13 @@ const UserActivityAvatar = memo(
|
|||||||
model.actions?.filter((action) => action.default == true) || []
|
model.actions?.filter((action) => action.default == true) || []
|
||||||
|
|
||||||
if (defaultModelActions.length >= 1 && getUserId(user)) {
|
if (defaultModelActions.length >= 1 && getUserId(user)) {
|
||||||
hyperlink = defaultModelActions[0].url(getUserId(user))
|
hyperlink = buildActionUrl(
|
||||||
|
model,
|
||||||
|
defaultModelActions[0],
|
||||||
|
getUserId(user),
|
||||||
|
'',
|
||||||
|
''
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
|
|||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import { buildActionUrl } from '../../../utils/modelActions'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import ActionsIcon from '../../Icons/ActionsIcon'
|
import ActionsIcon from '../../Icons/ActionsIcon'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
@ -152,8 +153,16 @@ const AlertsDisplay = ({
|
|||||||
onClick: ({ key }) => {
|
onClick: ({ key }) => {
|
||||||
const action = findActionByKey(filteredActions, key)
|
const action = findActionByKey(filteredActions, key)
|
||||||
|
|
||||||
if (action?.url) {
|
if (action) {
|
||||||
navigate(action.url(objectId))
|
navigate(
|
||||||
|
buildActionUrl(
|
||||||
|
getModelByName(objectType),
|
||||||
|
action,
|
||||||
|
objectId,
|
||||||
|
'',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
console.warn('No action found for key:', key)
|
console.warn('No action found for key:', key)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useMediaQuery } from 'react-responsive'
|
|||||||
import CopyButton from './CopyButton'
|
import CopyButton from './CopyButton'
|
||||||
import SpotlightTooltip from './SpotlightTooltip'
|
import SpotlightTooltip from './SpotlightTooltip'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import { buildActionUrl } from '../../../utils/modelActions'
|
||||||
|
|
||||||
const { Text, Link } = Typography
|
const { Text, Link } = Typography
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ const IdDisplay = ({
|
|||||||
model.actions?.filter((action) => action.default == true) || []
|
model.actions?.filter((action) => action.default == true) || []
|
||||||
|
|
||||||
if (defaultModelActions.length >= 1) {
|
if (defaultModelActions.length >= 1) {
|
||||||
hyperlink = defaultModelActions[0].url(id)
|
hyperlink = buildActionUrl(model, defaultModelActions[0], id, '', '')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
|||||||
56
src/components/Dashboard/common/ModelPage.jsx
Normal file
56
src/components/Dashboard/common/ModelPage.jsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Suspense, useContext, useEffect } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { Flex, Spin } from 'antd'
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import { getObjectIdFromSearch } from '../../../utils/modelActions'
|
||||||
|
import { useActions } from '../context/ActionsContext'
|
||||||
|
import { AuthContext } from '../context/AuthContext'
|
||||||
|
|
||||||
|
const ModelPage = ({ modelName, pageName }) => {
|
||||||
|
const model = getModelByName(modelName)
|
||||||
|
const page = model.pages?.find((p) => p.name === pageName)
|
||||||
|
const location = useLocation()
|
||||||
|
const { setCurrentObject, setCurrentObjectType } = useActions()
|
||||||
|
const { userProfile } = useContext(AuthContext)
|
||||||
|
const objectId = getObjectIdFromSearch(modelName, location.search)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentObjectType(modelName)
|
||||||
|
if (objectId) {
|
||||||
|
setCurrentObject({ _id: objectId, _user: userProfile })
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
setCurrentObject(null)
|
||||||
|
setCurrentObjectType(null)
|
||||||
|
}
|
||||||
|
}, [modelName, objectId, setCurrentObject, setCurrentObjectType, userProfile])
|
||||||
|
|
||||||
|
if (!page?.content) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Flex
|
||||||
|
justify='center'
|
||||||
|
align='center'
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
>
|
||||||
|
<Spin indicator={<LoadingOutlined spin />} />
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{page.content()}
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelPage.propTypes = {
|
||||||
|
modelName: PropTypes.string.isRequired,
|
||||||
|
pageName: PropTypes.string.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelPage
|
||||||
@ -6,6 +6,7 @@ import { useNavigate, useLocation } from 'react-router-dom'
|
|||||||
import { useActionsModal } from '../context/ActionsModalContext'
|
import { useActionsModal } from '../context/ActionsModalContext'
|
||||||
import KeyboardShortcut from './KeyboardShortcut'
|
import KeyboardShortcut from './KeyboardShortcut'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
|
import { buildActionUrl, stripPageActionParams } from '../../../utils/modelActions'
|
||||||
|
|
||||||
// Recursively filter actions based on visibleActions
|
// Recursively filter actions based on visibleActions
|
||||||
function filterActionsByVisibility(actions, visibleActions) {
|
function filterActionsByVisibility(actions, visibleActions) {
|
||||||
@ -69,22 +70,43 @@ function cleanDividers(items) {
|
|||||||
return cleaned
|
return cleaned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSameActionUrl(action, actionUrl, currentUrl, pathname, search) {
|
||||||
|
if (action.type === 'modal') {
|
||||||
|
return actionUrl === currentUrl
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
stripPageActionParams(
|
||||||
|
actionUrl.split('?')[0],
|
||||||
|
actionUrl.includes('?') ? actionUrl.split('?')[1] : ''
|
||||||
|
) === stripPageActionParams(pathname, search)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Recursively map actions to AntD Dropdown items
|
// Recursively map actions to AntD Dropdown items
|
||||||
function mapActionsToMenuItems(
|
function mapActionsToMenuItems(
|
||||||
actions,
|
actions,
|
||||||
currentUrlWithActions,
|
currentUrlWithActions,
|
||||||
id,
|
id,
|
||||||
objectData,
|
objectData,
|
||||||
userProfile
|
userProfile,
|
||||||
|
model,
|
||||||
|
pathname,
|
||||||
|
search
|
||||||
) {
|
) {
|
||||||
return cleanDividers(
|
return cleanDividers(
|
||||||
actions.map((action) => {
|
actions.map((action) => {
|
||||||
if (action.type === 'divider') {
|
if (action.type === 'divider') {
|
||||||
return { type: 'divider' }
|
return { type: 'divider' }
|
||||||
}
|
}
|
||||||
const actionUrl = action.url ? action.url(id) : undefined
|
const actionUrl = buildActionUrl(model, action, id, pathname, search)
|
||||||
|
|
||||||
var disabled = actionUrl && actionUrl === currentUrlWithActions
|
var disabled = isSameActionUrl(
|
||||||
|
action,
|
||||||
|
actionUrl,
|
||||||
|
currentUrlWithActions,
|
||||||
|
pathname,
|
||||||
|
search
|
||||||
|
)
|
||||||
var visible = true
|
var visible = true
|
||||||
|
|
||||||
if (action.disabled) {
|
if (action.disabled) {
|
||||||
@ -120,7 +142,10 @@ function mapActionsToMenuItems(
|
|||||||
currentUrlWithActions,
|
currentUrlWithActions,
|
||||||
id,
|
id,
|
||||||
objectData,
|
objectData,
|
||||||
userProfile
|
userProfile,
|
||||||
|
model,
|
||||||
|
pathname,
|
||||||
|
search
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
@ -128,13 +153,6 @@ function mapActionsToMenuItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stripActionParam = (pathname, search) => {
|
|
||||||
const params = new URLSearchParams(search)
|
|
||||||
params.delete('action')
|
|
||||||
const query = params.toString()
|
|
||||||
return pathname + (query ? `?${query}` : '')
|
|
||||||
}
|
|
||||||
|
|
||||||
const ObjectActions = ({
|
const ObjectActions = ({
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
@ -150,11 +168,6 @@ const ObjectActions = ({
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { showActionsModal } = useActionsModal()
|
const { showActionsModal } = useActionsModal()
|
||||||
const { userProfile } = useContext(AuthContext)
|
const { userProfile } = useContext(AuthContext)
|
||||||
// Get current url without 'action' param
|
|
||||||
const currentUrlWithoutActions = stripActionParam(
|
|
||||||
location.pathname,
|
|
||||||
location.search
|
|
||||||
)
|
|
||||||
|
|
||||||
// First filter by visibility, then by current URL
|
// First filter by visibility, then by current URL
|
||||||
const visibilityFilteredActions = filterActionsByVisibility(
|
const visibilityFilteredActions = filterActionsByVisibility(
|
||||||
@ -163,12 +176,23 @@ const ObjectActions = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const filteredActions = cleanDividers(
|
const filteredActions = cleanDividers(
|
||||||
visibilityFilteredActions.filter(
|
visibilityFilteredActions.filter((action) => {
|
||||||
(action) =>
|
if (action.type === 'divider') return true
|
||||||
action.type === 'divider' ||
|
const actionUrl = buildActionUrl(
|
||||||
typeof action.url !== 'function' ||
|
model,
|
||||||
action.url(id) !== currentUrlWithoutActions
|
action,
|
||||||
|
id,
|
||||||
|
location.pathname,
|
||||||
|
location.search
|
||||||
)
|
)
|
||||||
|
return !isSameActionUrl(
|
||||||
|
action,
|
||||||
|
actionUrl,
|
||||||
|
location.pathname + location.search,
|
||||||
|
location.pathname,
|
||||||
|
location.search
|
||||||
|
)
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const currentUrlWithActions = location.pathname + location.search
|
const currentUrlWithActions = location.pathname + location.search
|
||||||
@ -180,7 +204,10 @@ const ObjectActions = ({
|
|||||||
currentUrlWithActions,
|
currentUrlWithActions,
|
||||||
id,
|
id,
|
||||||
objectData,
|
objectData,
|
||||||
userProfile
|
userProfile,
|
||||||
|
model,
|
||||||
|
location.pathname,
|
||||||
|
location.search
|
||||||
),
|
),
|
||||||
onClick: (info) => {
|
onClick: (info) => {
|
||||||
// Find the action by key
|
// Find the action by key
|
||||||
@ -195,8 +222,10 @@ const ObjectActions = ({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const action = findAction(filteredActions, info.key)
|
const action = findAction(filteredActions, info.key)
|
||||||
if (action && action.url) {
|
if (action) {
|
||||||
navigate(action.url(id))
|
navigate(
|
||||||
|
buildActionUrl(model, action, id, location.pathname, location.search)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { LoadingOutlined } from '@ant-design/icons'
|
|||||||
import { useState, useEffect, useContext, useCallback, useRef } from 'react'
|
import { useState, useEffect, useContext, useCallback, useRef } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import { buildActionUrl } from '../../../utils/modelActions'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import merge from 'lodash/merge'
|
import merge from 'lodash/merge'
|
||||||
@ -182,7 +183,13 @@ const ObjectDisplay = ({
|
|||||||
const objectId = getStringId(objectData)
|
const objectId = getStringId(objectData)
|
||||||
|
|
||||||
if (defaultModelActions.length >= 1 && objectId) {
|
if (defaultModelActions.length >= 1 && objectId) {
|
||||||
hyperlink = defaultModelActions[0].url(objectId)
|
hyperlink = buildActionUrl(
|
||||||
|
model,
|
||||||
|
defaultModelActions[0],
|
||||||
|
objectId,
|
||||||
|
'',
|
||||||
|
''
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render name with hyperlink/spotlight support
|
// Render name with hyperlink/spotlight support
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import set from 'lodash/set'
|
|||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { useActions } from '../context/ActionsContext'
|
||||||
|
|
||||||
const arrayReplaceCustomizer = (objValue, srcValue) => {
|
const arrayReplaceCustomizer = (objValue, srcValue) => {
|
||||||
if (Array.isArray(srcValue)) {
|
if (Array.isArray(srcValue)) {
|
||||||
@ -86,9 +87,13 @@ const buildObjectFromEntries = (entries = []) => {
|
|||||||
* - children: function({
|
* - children: function({
|
||||||
* loading, isEditing, startEditing, cancelEditing, handleUpdate, form, formValid, objectData, setIsEditing, setObjectData
|
* loading, isEditing, startEditing, cancelEditing, handleUpdate, form, formValid, objectData, setIsEditing, setObjectData
|
||||||
* }) => ReactNode
|
* }) => ReactNode
|
||||||
|
* - setCurrentObject: boolean (sync object data to ActionsContext)
|
||||||
*/
|
*/
|
||||||
const ObjectForm = forwardRef(
|
const ObjectForm = forwardRef(
|
||||||
({ id, type, style, children, onEdit, onStateChange }, ref) => {
|
(
|
||||||
|
{ id, type, style, children, onEdit, onStateChange, setCurrentObject = false },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const [objectData, setObjectData] = useState(null)
|
const [objectData, setObjectData] = useState(null)
|
||||||
const serverObjectData = useRef(null)
|
const serverObjectData = useRef(null)
|
||||||
const onStateChangeRef = useRef(onStateChange)
|
const onStateChangeRef = useRef(onStateChange)
|
||||||
@ -118,6 +123,10 @@ const ObjectForm = forwardRef(
|
|||||||
flushFile
|
flushFile
|
||||||
} = useContext(ApiServerContext)
|
} = useContext(ApiServerContext)
|
||||||
const { token, userProfile } = useContext(AuthContext)
|
const { token, userProfile } = useContext(AuthContext)
|
||||||
|
const {
|
||||||
|
setCurrentObject: setActionsCurrentObject,
|
||||||
|
setCurrentObjectType: setActionsCurrentObjectType
|
||||||
|
} = useActions()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
@ -588,6 +597,32 @@ const ObjectForm = forwardRef(
|
|||||||
return () => clearTimeout(timeoutId)
|
return () => clearTimeout(timeoutId)
|
||||||
}, [objectData])
|
}, [objectData])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!setCurrentObject) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setActionsCurrentObjectType(type)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setActionsCurrentObject(null)
|
||||||
|
setActionsCurrentObjectType(null)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
setCurrentObject,
|
||||||
|
type,
|
||||||
|
setActionsCurrentObject,
|
||||||
|
setActionsCurrentObjectType
|
||||||
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!setCurrentObject || !objectData?._id || objectData._id !== id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setActionsCurrentObject(objectData)
|
||||||
|
}, [setCurrentObject, objectData, id, setActionsCurrentObject])
|
||||||
|
|
||||||
const startEditing = async () => {
|
const startEditing = async () => {
|
||||||
try {
|
try {
|
||||||
const latestActivities = await fetchObjectActivities(id, type)
|
const latestActivities = await fetchObjectActivities(id, type)
|
||||||
@ -814,7 +849,8 @@ ObjectForm.propTypes = {
|
|||||||
children: PropTypes.func.isRequired,
|
children: PropTypes.func.isRequired,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
onEdit: PropTypes.func,
|
onEdit: PropTypes.func,
|
||||||
onStateChange: PropTypes.func
|
onStateChange: PropTypes.func,
|
||||||
|
setCurrentObject: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectForm
|
export default ObjectForm
|
||||||
|
|||||||
@ -39,10 +39,11 @@ import ObjectCard from './ObjectCard'
|
|||||||
import FilterSidebar from './FilterSidebar'
|
import FilterSidebar from './FilterSidebar'
|
||||||
import XMarkIcon from '../../Icons/XMarkIcon'
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
import CheckIcon from '../../Icons/CheckIcon'
|
import CheckIcon from '../../Icons/CheckIcon'
|
||||||
import { useNavigate, useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
|
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import { ElectronContext } from '../context/ElectronContext'
|
import { ElectronContext } from '../context/ElectronContext'
|
||||||
|
import { useActions } from '../context/ActionsContext'
|
||||||
import ActionsIcon from '../../Icons/ActionsIcon'
|
import ActionsIcon from '../../Icons/ActionsIcon'
|
||||||
import FilterIcon from '../../Icons/FilterIcon'
|
import FilterIcon from '../../Icons/FilterIcon'
|
||||||
import ScrollBox from './ScrollBox'
|
import ScrollBox from './ScrollBox'
|
||||||
@ -130,10 +131,10 @@ const ObjectTable = forwardRef(
|
|||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const { token } = useContext(AuthContext)
|
const { token, userProfile } = useContext(AuthContext)
|
||||||
const { isElectron } = useContext(ElectronContext)
|
const { isElectron } = useContext(ElectronContext)
|
||||||
|
const { callAction } = useActions()
|
||||||
const onStateChangeRef = useRef(onStateChange)
|
const onStateChangeRef = useRef(onStateChange)
|
||||||
const { userProfile } = useContext(AuthContext)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onStateChangeRef.current = onStateChange
|
onStateChangeRef.current = onStateChange
|
||||||
}, [onStateChange])
|
}, [onStateChange])
|
||||||
@ -147,7 +148,6 @@ const ObjectTable = forwardRef(
|
|||||||
clearObjectActivity
|
clearObjectActivity
|
||||||
} = useContext(ApiServerContext)
|
} = useContext(ApiServerContext)
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
const navigate = useNavigate()
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const {
|
const {
|
||||||
getPersistedFilter,
|
getPersistedFilter,
|
||||||
@ -292,9 +292,11 @@ const ObjectTable = forwardRef(
|
|||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (action.url) {
|
callAction(
|
||||||
navigate(action.url(objectData._id))
|
action,
|
||||||
}
|
{ ...objectData, _user: userProfile },
|
||||||
|
type
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useContext } from 'react'
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import LogoutIcon from '../../Icons/LogoutIcon'
|
import LogoutIcon from '../../Icons/LogoutIcon'
|
||||||
import { User } from '../../../database/models/User'
|
import { User } from '../../../database/models/User'
|
||||||
|
import { buildActionUrl } from '../../../utils/modelActions'
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
@ -29,8 +30,8 @@ const UserProfilePopover = ({ onClose }) => {
|
|||||||
const runAction = (action) => {
|
const runAction = (action) => {
|
||||||
if (action.name === 'logout') {
|
if (action.name === 'logout') {
|
||||||
logout()
|
logout()
|
||||||
} else if (action.url && userProfile?._id) {
|
} else if (userProfile?._id) {
|
||||||
const url = action.url(userProfile._id)
|
const url = buildActionUrl(User, action, userProfile._id, '', '')
|
||||||
navigate(url)
|
navigate(url)
|
||||||
}
|
}
|
||||||
onClose?.()
|
onClose?.()
|
||||||
|
|||||||
222
src/components/Dashboard/context/ActionsContext.jsx
Normal file
222
src/components/Dashboard/context/ActionsContext.jsx
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Modal, Spin, Flex } from 'antd'
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
|
import {
|
||||||
|
buildActionUrl,
|
||||||
|
findAction,
|
||||||
|
getObjectIdFromSearch,
|
||||||
|
stripModalActionParams
|
||||||
|
} from '../../../utils/modelActions'
|
||||||
|
import { AuthContext } from './AuthContext'
|
||||||
|
|
||||||
|
const ActionsContext = createContext()
|
||||||
|
|
||||||
|
const ActionsProvider = ({ children }) => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const { userProfile } = useContext(AuthContext)
|
||||||
|
const [currentObject, setCurrentObject] = useState(null)
|
||||||
|
const [currentObjectType, setCurrentObjectType] = useState(null)
|
||||||
|
const [modalAction, setModalAction] = useState(null)
|
||||||
|
const [onModalOk, setOnModalOk] = useState(null)
|
||||||
|
const lastHandledAction = useRef(null)
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(location.search)
|
||||||
|
const actionName = searchParams.get('action')
|
||||||
|
const actionObjectType = searchParams.get('actionObjectType')
|
||||||
|
|
||||||
|
const clearAction = useCallback(() => {
|
||||||
|
const nextUrl = stripModalActionParams(location.pathname, location.search)
|
||||||
|
if (nextUrl !== location.pathname + location.search) {
|
||||||
|
navigate(nextUrl, { replace: true })
|
||||||
|
}
|
||||||
|
setModalAction(null)
|
||||||
|
lastHandledAction.current = null
|
||||||
|
}, [location.pathname, location.search, navigate])
|
||||||
|
|
||||||
|
const handleModalOk = useCallback(() => {
|
||||||
|
onModalOk?.()
|
||||||
|
clearAction()
|
||||||
|
}, [clearAction, onModalOk])
|
||||||
|
|
||||||
|
const callAction = useCallback(
|
||||||
|
(action, objectData, objectType) => {
|
||||||
|
setCurrentObject(objectData)
|
||||||
|
setCurrentObjectType(objectType)
|
||||||
|
const model = getModelByName(objectType)
|
||||||
|
const url = buildActionUrl(
|
||||||
|
model,
|
||||||
|
action,
|
||||||
|
objectData._id,
|
||||||
|
location.pathname,
|
||||||
|
location.search
|
||||||
|
)
|
||||||
|
navigate(url)
|
||||||
|
},
|
||||||
|
[location.pathname, location.search, navigate]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!actionObjectType) return
|
||||||
|
|
||||||
|
const model = getModelByName(actionObjectType)
|
||||||
|
if (!model.pages?.length) return
|
||||||
|
|
||||||
|
const onPage = model.pages.some(
|
||||||
|
(page) => location.pathname === `${model.url}/${page.name}`
|
||||||
|
)
|
||||||
|
if (!onPage) return
|
||||||
|
|
||||||
|
if (actionName) {
|
||||||
|
const action = findAction(model, actionName)
|
||||||
|
if (action?.type === 'modal') return
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search)
|
||||||
|
params.delete('actionObjectType')
|
||||||
|
const query = params.toString()
|
||||||
|
const nextUrl = location.pathname + (query ? `?${query}` : '')
|
||||||
|
if (nextUrl !== location.pathname + location.search) {
|
||||||
|
navigate(nextUrl, { replace: true })
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
actionName,
|
||||||
|
actionObjectType,
|
||||||
|
location.pathname,
|
||||||
|
location.search,
|
||||||
|
navigate
|
||||||
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!actionName || !actionObjectType) {
|
||||||
|
setModalAction(null)
|
||||||
|
lastHandledAction.current = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionKey = `${actionObjectType}:${actionName}:${location.pathname}${location.search}`
|
||||||
|
if (lastHandledAction.current === actionKey) return
|
||||||
|
|
||||||
|
const model = getModelByName(actionObjectType)
|
||||||
|
const action = findAction(model, actionName)
|
||||||
|
if (!action) return
|
||||||
|
|
||||||
|
if (action.type === 'modal') {
|
||||||
|
lastHandledAction.current = actionKey
|
||||||
|
setModalAction(action)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === 'page') {
|
||||||
|
const pageName = action.pageName || action.name
|
||||||
|
const objectId =
|
||||||
|
getObjectIdFromSearch(actionObjectType, location.search) ||
|
||||||
|
currentObject?._id
|
||||||
|
if (!objectId) return
|
||||||
|
|
||||||
|
const expectedPath = `${model.url}/${pageName}`
|
||||||
|
if (location.pathname === expectedPath) {
|
||||||
|
lastHandledAction.current = actionKey
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHandledAction.current = actionKey
|
||||||
|
const url = buildActionUrl(
|
||||||
|
model,
|
||||||
|
action,
|
||||||
|
objectId,
|
||||||
|
location.pathname,
|
||||||
|
location.search
|
||||||
|
)
|
||||||
|
navigate(url, { replace: true })
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
actionName,
|
||||||
|
actionObjectType,
|
||||||
|
currentObject,
|
||||||
|
location.pathname,
|
||||||
|
location.search,
|
||||||
|
navigate
|
||||||
|
])
|
||||||
|
|
||||||
|
const modalObjectData = currentObject
|
||||||
|
? { ...currentObject, _user: userProfile }
|
||||||
|
: null
|
||||||
|
|
||||||
|
const [modelWidth, setModelWidth] = useState(520)
|
||||||
|
useEffect(() => {
|
||||||
|
if (modalAction?.modalWidth) {
|
||||||
|
setModelWidth(modalAction.modalWidth)
|
||||||
|
}
|
||||||
|
}, [modalAction?.modalWidth])
|
||||||
|
|
||||||
|
const [modalCentered, setModalCentered] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
if (modalAction?.modalCentered !== undefined) {
|
||||||
|
setModalCentered(modalAction.modalCentered)
|
||||||
|
}
|
||||||
|
}, [modalAction?.modalCentered])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionsContext.Provider
|
||||||
|
value={{
|
||||||
|
currentObject,
|
||||||
|
currentObjectType,
|
||||||
|
setCurrentObject,
|
||||||
|
setCurrentObjectType,
|
||||||
|
callAction,
|
||||||
|
clearAction,
|
||||||
|
setOnModalOk
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Modal
|
||||||
|
open={modalAction != null}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
width={modelWidth}
|
||||||
|
onCancel={clearAction}
|
||||||
|
footer={null}
|
||||||
|
centered={modalCentered}
|
||||||
|
>
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Flex
|
||||||
|
justify='center'
|
||||||
|
align='center'
|
||||||
|
style={{
|
||||||
|
height: modalCentered ? '120px' : '357px',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin indicator={<LoadingOutlined spin />} />
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{modalAction?.content && modalObjectData
|
||||||
|
? modalAction.content(modalObjectData, { onOk: handleModalOk })
|
||||||
|
: null}
|
||||||
|
</Suspense>
|
||||||
|
</Modal>
|
||||||
|
{children}
|
||||||
|
</ActionsContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionsProvider.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const useActions = () => useContext(ActionsContext)
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
export { ActionsProvider, ActionsContext, useActions }
|
||||||
@ -12,16 +12,15 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
|||||||
|
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import { AuthContext } from './AuthContext'
|
import { AuthContext } from './AuthContext'
|
||||||
|
import {
|
||||||
|
buildActionUrl,
|
||||||
|
stripActionParams
|
||||||
|
} from '../../../utils/modelActions'
|
||||||
|
|
||||||
const ActionsModalContext = createContext()
|
const ActionsModalContext = createContext()
|
||||||
|
|
||||||
// Remove the "action" query param from a URL so we don't navigate to the same URL again
|
// Remove action query params from a URL so we don't navigate to the same URL again
|
||||||
const stripActionParam = (pathname, search) => {
|
const stripActionParam = stripActionParams
|
||||||
const params = new URLSearchParams(search)
|
|
||||||
params.delete('action')
|
|
||||||
const query = params.toString()
|
|
||||||
return pathname + (query ? `?${query}` : '')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flatten nested actions (including children) into a single list
|
// Flatten nested actions (including children) into a single list
|
||||||
const flattenActions = (actions, parentLabel = '') => {
|
const flattenActions = (actions, parentLabel = '') => {
|
||||||
@ -34,7 +33,10 @@ const flattenActions = (actions, parentLabel = '') => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasUrl = typeof action.url === 'function'
|
const hasUrl =
|
||||||
|
typeof action.url === 'function' ||
|
||||||
|
action.type === 'page' ||
|
||||||
|
action.type === 'modal'
|
||||||
const hasChildren =
|
const hasChildren =
|
||||||
Array.isArray(action.children) && action.children.length > 0
|
Array.isArray(action.children) && action.children.length > 0
|
||||||
|
|
||||||
@ -122,7 +124,13 @@ const ActionsModalProvider = ({ children }) => {
|
|||||||
if (!action) return true
|
if (!action) return true
|
||||||
|
|
||||||
let disabled = false
|
let disabled = false
|
||||||
const url = action.url ? action.url(id) : undefined
|
const url = buildActionUrl(
|
||||||
|
getModelByName(context.type),
|
||||||
|
action,
|
||||||
|
id,
|
||||||
|
location.pathname,
|
||||||
|
location.search
|
||||||
|
)
|
||||||
|
|
||||||
// Match ObjectActions default disabling behaviour
|
// Match ObjectActions default disabling behaviour
|
||||||
if (url && url === currentUrlWithoutActions) {
|
if (url && url === currentUrlWithoutActions) {
|
||||||
@ -174,11 +182,18 @@ const ActionsModalProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const runAction = (action) => {
|
const runAction = (action) => {
|
||||||
if (!action || typeof action.url !== 'function') return
|
if (!action) return
|
||||||
if (getActionDisabled(action)) return
|
if (getActionDisabled(action)) return
|
||||||
|
|
||||||
const { id } = context
|
const { id } = context
|
||||||
const targetUrl = action.url(id)
|
const model = getModelByName(context.type)
|
||||||
|
const targetUrl = buildActionUrl(
|
||||||
|
model,
|
||||||
|
action,
|
||||||
|
id,
|
||||||
|
location.pathname,
|
||||||
|
location.search
|
||||||
|
)
|
||||||
if (targetUrl && targetUrl !== '#') {
|
if (targetUrl && targetUrl !== '#') {
|
||||||
navigate(targetUrl)
|
navigate(targetUrl)
|
||||||
hideActionsModal()
|
hideActionsModal()
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import {
|
|||||||
getModelByPrefix,
|
getModelByPrefix,
|
||||||
searchModelsByLabel
|
searchModelsByLabel
|
||||||
} from '../../../database/ObjectModels'
|
} from '../../../database/ObjectModels'
|
||||||
|
import { buildActionUrl } from '../../../utils/modelActions'
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
import MenuIcon from '../../Icons/MenuIcon'
|
import MenuIcon from '../../Icons/MenuIcon'
|
||||||
import { ApiServerContext } from './ApiServerContext'
|
import { ApiServerContext } from './ApiServerContext'
|
||||||
@ -283,11 +284,10 @@ const SpotlightContent = ({
|
|||||||
const getDefaultRowAction = (model) =>
|
const getDefaultRowAction = (model) =>
|
||||||
getRowActions(model).find((action) => action.default)
|
getRowActions(model).find((action) => action.default)
|
||||||
|
|
||||||
const triggerRowAction = (action, item) => {
|
const triggerRowAction = (action, item, model) => {
|
||||||
if (action && action.url) {
|
if (action) {
|
||||||
// Try to get the id (support _id or id)
|
|
||||||
const itemId = item._id || item.id
|
const itemId = item._id || item.id
|
||||||
const url = action.url(itemId)
|
const url = buildActionUrl(model, action, itemId, '', '')
|
||||||
if (url && url !== '#') {
|
if (url && url !== '#') {
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
openInternalUrl(url)
|
openInternalUrl(url)
|
||||||
@ -314,7 +314,7 @@ const SpotlightContent = ({
|
|||||||
? getDefaultRowAction(model)
|
? getDefaultRowAction(model)
|
||||||
: null
|
: null
|
||||||
if (defaultAction) {
|
if (defaultAction) {
|
||||||
triggerRowAction(defaultAction, item)
|
triggerRowAction(defaultAction, item, model)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -332,7 +332,7 @@ const SpotlightContent = ({
|
|||||||
? getDefaultRowAction(model)
|
? getDefaultRowAction(model)
|
||||||
: null
|
: null
|
||||||
if (defaultAction) {
|
if (defaultAction) {
|
||||||
triggerRowAction(defaultAction, item)
|
triggerRowAction(defaultAction, item, model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -539,7 +539,7 @@ const SpotlightContent = ({
|
|||||||
}
|
}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
type={'text'}
|
type={'text'}
|
||||||
onClick={() => triggerRowAction(action, item)}
|
onClick={() => triggerRowAction(action, item, model)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{rowActions
|
{rowActions
|
||||||
@ -556,7 +556,7 @@ const SpotlightContent = ({
|
|||||||
}
|
}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
type={'text'}
|
type={'text'}
|
||||||
onClick={() => triggerRowAction(action, item)}
|
onClick={() => triggerRowAction(action, item, model)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{shortcutText && <Text keyboard>{shortcutText}</Text>}
|
{shortcutText && <Text keyboard>{shortcutText}</Text>}
|
||||||
|
|||||||
20
src/routes/ModelRoutes.jsx
Normal file
20
src/routes/ModelRoutes.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Route } from 'react-router-dom'
|
||||||
|
import { objectModels } from '../database/ObjectModels'
|
||||||
|
import { getModelRoutePath } from '../utils/modelActions'
|
||||||
|
import ModelPage from '../components/Dashboard/common/ModelPage'
|
||||||
|
|
||||||
|
const ModelRoutes = objectModels
|
||||||
|
.filter((model) => model.pages?.length > 0)
|
||||||
|
.flatMap((model) =>
|
||||||
|
model.pages.map((page) => (
|
||||||
|
<Route
|
||||||
|
key={`${model.name}-${page.name}`}
|
||||||
|
path={`${getModelRoutePath(model)}/${page.name}`}
|
||||||
|
element={
|
||||||
|
<ModelPage modelName={model.name} pageName={page.name} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ModelRoutes
|
||||||
@ -4,3 +4,4 @@ export { default as FinanceRoutes } from './FinanceRoutes'
|
|||||||
export { default as SalesRoutes } from './SalesRoutes'
|
export { default as SalesRoutes } from './SalesRoutes'
|
||||||
export { default as ManagementRoutes } from './ManagementRoutes'
|
export { default as ManagementRoutes } from './ManagementRoutes'
|
||||||
export { default as DeveloperRoutes } from './DeveloperRoutes'
|
export { default as DeveloperRoutes } from './DeveloperRoutes'
|
||||||
|
export { default as ModelRoutes } from './ModelRoutes'
|
||||||
|
|||||||
72
src/utils/modelActions.js
Normal file
72
src/utils/modelActions.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
export function getObjectIdParamName(modelName) {
|
||||||
|
return `${modelName}Id`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getObjectIdFromSearch(modelName, search) {
|
||||||
|
const params = new URLSearchParams(search)
|
||||||
|
return params.get(getObjectIdParamName(modelName))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelRoutePath(model) {
|
||||||
|
return model.url.replace(/^\/dashboard\//, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildActionUrl(model, action, objectId, pathname, search = '') {
|
||||||
|
if (typeof action.url === 'function') {
|
||||||
|
return action.url(objectId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const idParam = getObjectIdParamName(model.name)
|
||||||
|
const pageName = action.pageName || action.name
|
||||||
|
|
||||||
|
if (action.type === 'page') {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set(idParam, objectId)
|
||||||
|
if (action.name !== pageName) {
|
||||||
|
params.set('action', action.name)
|
||||||
|
}
|
||||||
|
return `/dashboard/${getModelRoutePath(model)}/${pageName}?${params.toString()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams(search)
|
||||||
|
params.set(idParam, objectId)
|
||||||
|
params.set('action', action.name)
|
||||||
|
params.set('actionObjectType', model.name)
|
||||||
|
const query = params.toString()
|
||||||
|
return `${pathname}${query ? `?${query}` : ''}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stripPageActionParams(pathname, search) {
|
||||||
|
const params = new URLSearchParams(search)
|
||||||
|
params.delete('action')
|
||||||
|
const query = params.toString()
|
||||||
|
return pathname + (query ? `?${query}` : '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stripModalActionParams(pathname, search) {
|
||||||
|
const params = new URLSearchParams(search)
|
||||||
|
params.delete('action')
|
||||||
|
params.delete('actionObjectType')
|
||||||
|
const query = params.toString()
|
||||||
|
return pathname + (query ? `?${query}` : '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stripActionParams(pathname, search) {
|
||||||
|
return stripPageActionParams(pathname, search)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findAction(model, actionName) {
|
||||||
|
const findInActions = (actions) => {
|
||||||
|
if (!Array.isArray(actions)) return null
|
||||||
|
for (const action of actions) {
|
||||||
|
if (action.type === 'divider') continue
|
||||||
|
if ((action.key || action.name) === actionName) return action
|
||||||
|
if (action.children) {
|
||||||
|
const found = findInActions(action.children)
|
||||||
|
if (found) return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return findInActions(model?.actions)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user