Tom Butcher 24c06c25d3 Refactor JavaScript Language Support and Enhance Autocomplete Functionality
- Consolidated JavaScript completion sources into a new module, improving organization and maintainability.
- Updated the `fcTemplateLang` to utilize the new `javascriptCompletionSources` for enhanced autocomplete capabilities.
- Removed redundant imports and streamlined the code in various components, including Invoices, Payments, TaxRecords, and Inventory sections, by replacing dropdown actions with a unified `ObjectActions` component.
- Improved the handling of object actions across multiple inventory and finance components, enhancing user experience and code consistency.
2026-08-20 19:35:09 +01:00

238 lines
6.4 KiB
JavaScript

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,
resolveAction,
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' || action?.type === 'alias') 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' || action.type === 'alias') {
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 }
: { _user: userProfile }
const resolvedModalAction =
modalAction?.type === 'alias' ? resolveAction(modalAction) : modalAction
const [modelWidth, setModelWidth] = useState(520)
useEffect(() => {
if (resolvedModalAction?.modalWidth) {
setModelWidth(resolvedModalAction.modalWidth)
}
}, [resolvedModalAction?.modalWidth])
const [modalCentered, setModalCentered] = useState(false)
useEffect(() => {
if (resolvedModalAction?.modalCentered !== undefined) {
setModalCentered(resolvedModalAction.modalCentered)
}
}, [resolvedModalAction?.modalCentered])
const modalContentData =
modalAction?.type === 'alias'
? typeof modalAction.objectData === 'function'
? modalAction.objectData(modalObjectData)
: (modalAction.objectData ?? {})
: resolvedModalAction?.name === 'new'
? {}
: modalObjectData
return (
<ActionsContext.Provider
value={{
currentObject,
currentObjectType,
setCurrentObject,
setCurrentObjectType,
callAction,
clearAction,
setOnModalOk
}}
>
<Modal
open={resolvedModalAction != 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>
}
>
{resolvedModalAction?.content
? resolvedModalAction.content(modalContentData, {
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 }