diff --git a/packages/client/src/v2-events/features/events/actions/quick-actions/useQuickActionModal.tsx b/packages/client/src/v2-events/features/events/actions/quick-actions/useQuickActionModal.tsx index ed8051893bd..7c776998a1d 100644 --- a/packages/client/src/v2-events/features/events/actions/quick-actions/useQuickActionModal.tsx +++ b/packages/client/src/v2-events/features/events/actions/quick-actions/useQuickActionModal.tsx @@ -18,12 +18,15 @@ import { PrimaryButton, TertiaryButton } from '@opencrvs/components/lib/buttons' +import { Icon, IconProps } from '@opencrvs/components/src/Icon' import { ActionType, CustomActionConfig, + EventConfig, EventIndex, FieldConfig, FieldUpdateValue, + getActionConfig, runFieldValidations } from '@opencrvs/commons/client' import { buttonMessages } from '@client/i18n/messages' @@ -34,16 +37,25 @@ import { useUserAllowedActions } from '../../../workqueues/EventOverview/compone import { useModal } from '../../../../hooks/useModal' import { useEvents } from '../../useEvents/useEvents' import { actionLabels } from '../../../workqueues/EventOverview/components/useAllowedActionConfigurations' +import { useEventConfiguration } from '../../useEventConfiguration' import { validate } from './validate' import { register } from './register' import { archive } from './archive' +const quickActions = { + [ActionType.VALIDATE]: validate, + [ActionType.REGISTER]: register, + [ActionType.ARCHIVE]: archive +} as const satisfies Partial> + interface ModalConfig { label?: MessageDescriptor description?: MessageDescriptor confirmButtonType?: 'primary' | 'danger' confirmButtonLabel?: MessageDescriptor fields?: FieldConfig[] + actionType?: keyof typeof quickActions + icon?: IconProps['name'] } export interface QuickActionConfig { @@ -61,12 +73,6 @@ export interface QuickActionConfig { }) => void | Promise } -const quickActions = { - [ActionType.VALIDATE]: validate, - [ActionType.REGISTER]: register, - [ActionType.ARCHIVE]: archive -} as const satisfies Partial> - interface ModalResult { /** Whether the modal was confirmed/accepted or not */ result: boolean @@ -74,6 +80,12 @@ interface ModalResult { values?: Record } +const DefaultIcons = { + [ActionType.VALIDATE]: 'PencilLine', + [ActionType.REGISTER]: 'PencilLine', + [ActionType.ARCHIVE]: 'Archive' +} as const + function QuickActionModal({ close, config @@ -130,6 +142,17 @@ function QuickActionModal({ id={`quick-action-modal-${config.label.id}`} isOpen={true} title={intl.formatMessage(config.label)} + titleIcon={ + + } variant={'large'} width={898} onClose={() => close({ result: false })} @@ -152,6 +175,7 @@ export function useQuickActionModal(event: EventIndex) { const navigate = useNavigate() const { actions, customActions } = useEvents() const { isActionAllowed } = useUserAllowedActions(event.type) + const { eventConfiguration } = useEventConfiguration(event.type) const onQuickAction = async ( actionType: keyof typeof quickActions, @@ -159,8 +183,17 @@ export function useQuickActionModal(event: EventIndex) { ) => { const config = quickActions[actionType] const label = actionLabels[actionType] + const actionConfig = getActionConfig({ actionType, eventConfiguration }) const { result } = await openModal((close) => ( - + )) // On confirmed modal, we will: @@ -210,7 +243,8 @@ export function useCustomActionModal(event: EventIndex) { ...customActionConfigBase, label: actionConfig.label, description: actionConfig.supportingCopy, - fields: actionConfig.form + fields: actionConfig.form, + icon: actionConfig.icon }} /> )) diff --git a/packages/client/src/v2-events/features/workqueues/EventOverview/components/useAllowedActionConfigurations.tsx b/packages/client/src/v2-events/features/workqueues/EventOverview/components/useAllowedActionConfigurations.tsx index ef52de69915..3e97ea41c8d 100644 --- a/packages/client/src/v2-events/features/workqueues/EventOverview/components/useAllowedActionConfigurations.tsx +++ b/packages/client/src/v2-events/features/workqueues/EventOverview/components/useAllowedActionConfigurations.tsx @@ -322,6 +322,10 @@ function useViewableActionConfigurations( : actionLabels[ActionType.VALIDATE] } + const getAction = (type: ActionType) => { + return eventConfiguration.actions.find((action) => action.type === type) + } + /** * Configuration should be kept simple. Actions should do one thing, or navigate to one place. * If you need to extend the functionality, consider whether it can be done elsewhere. @@ -329,6 +333,7 @@ function useViewableActionConfigurations( return { modals: [assignModal, deleteModal, rejectionModal, quickActionModal], config: { + // Core actions [ActionType.ASSIGN]: { label: actionLabels[ActionType.ASSIGN], icon: 'PushPin' as const, @@ -373,8 +378,36 @@ function useViewableActionConfigurations( }, disabled: !isOnline }, - [ActionType.DECLARE]: { + [ActionType.ARCHIVE]: { + label: actionLabels[ActionType.ARCHIVE], + icon: 'Archive' as const, + onClick: async (workqueue) => + onQuickAction(ActionType.ARCHIVE, workqueue), + disabled: !isDownloadedAndAssignedToUser + }, + [ActionType.MARK_AS_DUPLICATE]: { + label: actionLabels[ActionType.MARK_AS_DUPLICATE], icon: 'PencilLine' as const, + onClick: (workqueue?: string) => { + clearEphemeralFormState() + return navigate( + ROUTES.V2.EVENTS.REVIEW_POTENTIAL_DUPLICATE.buildPath( + { eventId }, + { workqueue } + ) + ) + }, + disabled: !isDownloadedAndAssignedToUser || isAssignmentInProgress + }, + [ActionType.DELETE]: { + label: actionLabels[ActionType.DELETE], + icon: 'Trash' as const, + onClick: onDelete, + disabled: !isDownloadedAndAssignedToUser + }, + // Configurable event actions + [ActionType.DECLARE]: { + icon: getAction(ActionType.DECLARE)?.icon ?? ('PencilLine' as const), label: isReviewingDeclaration ? reviewLabel : actionLabels[ActionType.DECLARE], @@ -392,7 +425,7 @@ function useViewableActionConfigurations( }, [ActionType.REJECT]: { label: actionLabels[ActionType.REJECT], - icon: 'FileX', + icon: getAction(ActionType.REJECT)?.icon ?? 'FileX', onClick: async (workqueue) => handleRejection(() => workqueue @@ -406,7 +439,7 @@ function useViewableActionConfigurations( }, [ActionType.VALIDATE]: { label: resolveValidateLabel(), - icon: 'PencilLine' as const, + icon: getAction(ActionType.VALIDATE)?.icon ?? ('PencilLine' as const), onClick: async (workqueue) => { if (userMayRegister) { return onQuickAction(ActionType.REGISTER, workqueue) @@ -421,18 +454,11 @@ function useViewableActionConfigurations( ctaLabel: reviewLabel, disabled: !isDownloadedAndAssignedToUser }, - [ActionType.ARCHIVE]: { - label: actionLabels[ActionType.ARCHIVE], - icon: 'Archive' as const, - onClick: async (workqueue) => - onQuickAction(ActionType.ARCHIVE, workqueue), - disabled: !isDownloadedAndAssignedToUser - }, [ActionType.REGISTER]: { label: isReviewingIncompleteDeclaration ? reviewLabel : actionLabels[ActionType.REGISTER], - icon: 'PencilLine' as const, + icon: getAction(ActionType.REGISTER)?.icon ?? ('PencilLine' as const), onClick: async (workqueue) => onQuickAction(ActionType.REGISTER, workqueue), ctaLabel: reviewLabel, @@ -443,23 +469,10 @@ function useViewableActionConfigurations( disabled: !isDownloadedAndAssignedToUser, hidden: !event.flags.includes('validated') }, - [ActionType.MARK_AS_DUPLICATE]: { - label: actionLabels[ActionType.MARK_AS_DUPLICATE], - icon: 'PencilLine' as const, - onClick: (workqueue?: string) => { - clearEphemeralFormState() - return navigate( - ROUTES.V2.EVENTS.REVIEW_POTENTIAL_DUPLICATE.buildPath( - { eventId }, - { workqueue } - ) - ) - }, - disabled: !isDownloadedAndAssignedToUser || isAssignmentInProgress - }, [ActionType.PRINT_CERTIFICATE]: { label: actionLabels[ActionType.PRINT_CERTIFICATE], - icon: 'Printer' as const, + icon: + getAction(ActionType.PRINT_CERTIFICATE)?.icon ?? ('Printer' as const), onClick: (workqueue?: string) => { clearEphemeralFormState() return navigate( @@ -472,15 +485,11 @@ function useViewableActionConfigurations( disabled: !isDownloadedAndAssignedToUser || eventIsWaitingForCorrection, hidden: eventIsWaitingForCorrection }, - [ActionType.DELETE]: { - label: actionLabels[ActionType.DELETE], - icon: 'Trash' as const, - onClick: onDelete, - disabled: !isDownloadedAndAssignedToUser - }, [ActionType.REQUEST_CORRECTION]: { label: actionLabels[ActionType.REQUEST_CORRECTION], - icon: 'NotePencil' as const, + icon: + getAction(ActionType.REQUEST_CORRECTION)?.icon ?? + ('NotePencil' as const), onClick: (workqueue?: string) => { const correctionPages = eventConfiguration.actions.find( (action) => action.type === ActionType.REQUEST_CORRECTION @@ -586,7 +595,7 @@ function useCustomActionConfigs( ) .map((action) => ({ label: action.label, - icon: 'PencilLine' as const, + icon: action.icon ?? ('PencilLine' as const), onClick: async (workqueue?: string) => onCustomAction(action, workqueue), disabled: !isDownloadedAndAssignedToUser, diff --git a/packages/commons/src/events/ActionConfig.ts b/packages/commons/src/events/ActionConfig.ts index 983f6040935..33542b126e9 100644 --- a/packages/commons/src/events/ActionConfig.ts +++ b/packages/commons/src/events/ActionConfig.ts @@ -16,6 +16,7 @@ import { ActionFormConfig } from './FormConfig' import { DeduplicationConfig } from './DeduplicationConfig' import { ActionFlagConfig } from './Flag' import { ActionConditional } from './Conditional' +import { AvailableIcons } from '../icons' export const DeclarationReviewConfig = z .object({ @@ -41,6 +42,7 @@ export const ActionConfigBase = z.object({ supportingCopy: TranslationConfig.optional().describe( 'Text displayed on the confirmation' ), + icon: AvailableIcons.describe('Icon representing the action').optional(), conditionals: z .array(ActionConditional) .optional()