- 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.
238 lines
6.4 KiB
JavaScript
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 }
|