import { createElement, useContext, useEffect, useRef } from 'react' import { Dropdown, Button } from 'antd' import { getModelByName } from '../../../database/ObjectModels' import PropTypes from 'prop-types' import { useNavigate, useLocation } from 'react-router-dom' import { useActionsModal } from '../context/ActionsModalContext' import { useActions } from '../context/ActionsContext' import KeyboardShortcut from './KeyboardShortcut' import { AuthContext } from '../context/AuthContext' import { actionVisibleOnPage, buildActionUrl, getActionPermissionTarget, resolveAction, stripPageActionParams } from '../../../utils/modelActions' import { hasActionPermission } from '../../../database/permissions' function filterActionsByVisibility(actions, visibleActions) { if (!visibleActions) return actions return actions.filter((action) => { if (action.type === 'divider') { return true } const actionKey = action.key || action.name const isVisible = visibleActions[actionKey] !== false if (action.children && Array.isArray(action.children)) { const filteredChildren = filterActionsByVisibility( action.children, visibleActions ) action.children = filteredChildren return ( isVisible && (filteredChildren.length > 0 || visibleActions[actionKey] === true) ) } return isVisible }) } function cleanDividers(items) { if (!Array.isArray(items) || items.length === 0) return [] const cleaned = [] for (const item of items) { if (!item) continue if (item.type === 'divider') { if (cleaned.length === 0) continue if (cleaned[cleaned.length - 1]?.type === 'divider') continue cleaned.push(item) continue } if (item.children && Array.isArray(item.children)) { const children = cleanDividers(item.children) if (children.length === 0) continue cleaned.push({ ...item, children }) continue } cleaned.push(item) } if (cleaned[cleaned.length - 1]?.type === 'divider') { cleaned.pop() } return cleaned } function isSameActionUrl(action, actionUrl, currentUrl, pathname, search) { if (action.type === 'modal' || action.type === 'alias') { return actionUrl === currentUrl } return actionUrl === stripPageActionParams(pathname, search) } function actionDenied(userProfile, model, action, parentDenied = false) { if (parentDenied) return true const { model: permissionModel, actionName } = getActionPermissionTarget( action, model ) return !hasActionPermission(userProfile, permissionModel, actionName) } function mapActionsToMenuItems( actions, currentUrlWithActions, id, objectData, userProfile, model, pathname, search, parentDenied = false ) { return cleanDividers( actions.map((action) => { if (action.type === 'divider') { return { type: 'divider' } } const displayAction = resolveAction(action) const actionUrl = buildActionUrl(model, action, id, pathname, search) const denied = actionDenied(userProfile, model, action, parentDenied) var disabled = denied || isSameActionUrl( action, actionUrl, currentUrlWithActions, pathname, search ) var visible = true if (action.disabled) { if (typeof action.disabled === 'function') { disabled = denied || action.disabled({ ...objectData, _user: userProfile }) } else { disabled = denied || action.disabled } } if (action.visible) { if (typeof action.visible === 'function') { visible = action.visible(objectData) } else { visible = action.visible } } if (visible != true) { return null } const item = { key: action.key || action.name, label: displayAction.label, danger: action?.danger || false, icon: displayAction.icon ? createElement(displayAction.icon) : undefined, disabled } if (action.children && Array.isArray(action.children)) { item.children = mapActionsToMenuItems( action.children, currentUrlWithActions, id, objectData, userProfile, model, pathname, search, denied ) } return item }) ) } const ObjectActions = ({ type, id, objectData = {}, pageName = 'info', onReload, disabled = false, buttonProps = {}, visibleActions = {}, ...dropdownProps }) => { const model = getModelByName(type) const actions = model.actions || [] const navigate = useNavigate() const location = useLocation() const { showActionsModal } = useActionsModal() const { setOnModalOk } = useActions() const { userProfile } = useContext(AuthContext) const onReloadRef = useRef(onReload) onReloadRef.current = onReload useEffect(() => { if (pageName !== 'list') return setOnModalOk(() => () => onReloadRef.current?.()) return () => setOnModalOk(null) }, [pageName, setOnModalOk]) const pageActions = actions.filter((action) => actionVisibleOnPage(action, pageName) ) const visibilityFilteredActions = filterActionsByVisibility( pageActions, visibleActions ) const filteredActions = cleanDividers( visibilityFilteredActions.filter((action) => { if (action.type === 'divider') return true const actionUrl = buildActionUrl( model, 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 openActionsModal = () => showActionsModal(id, type, objectData, pageName) const menu = { items: mapActionsToMenuItems( filteredActions, currentUrlWithActions, id, objectData, userProfile, model, location.pathname, location.search ), onClick: (info) => { const findMenuAction = (acts, key, parentDenied = false) => { for (const act of acts) { if (act.type === 'divider') continue const denied = actionDenied(userProfile, model, act, parentDenied) if ((act.key || act.name) === key) { return denied ? null : act } if (act.children) { const found = findMenuAction(act.children, key, denied) if (found) return found } } return null } const action = findMenuAction(filteredActions, info.key) if (!action) return navigate( buildActionUrl(model, action, id, location.pathname, location.search) ) } } return ( ) } ObjectActions.propTypes = { type: PropTypes.string.isRequired, objectData: PropTypes.object, id: PropTypes.string, pageName: PropTypes.string, onReload: PropTypes.func, disabled: PropTypes.bool, buttonProps: PropTypes.object, buttonLabel: PropTypes.string, visibleActions: PropTypes.object } export default ObjectActions