Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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<Record<ActionType, QuickActionConfig>>

interface ModalConfig {
label?: MessageDescriptor
description?: MessageDescriptor
confirmButtonType?: 'primary' | 'danger'
confirmButtonLabel?: MessageDescriptor
fields?: FieldConfig[]
actionType?: keyof typeof quickActions
icon?: IconProps['name']
}

export interface QuickActionConfig {
Expand All @@ -61,19 +73,19 @@ export interface QuickActionConfig {
}) => void | Promise<void>
}

const quickActions = {
[ActionType.VALIDATE]: validate,
[ActionType.REGISTER]: register,
[ActionType.ARCHIVE]: archive
} as const satisfies Partial<Record<ActionType, QuickActionConfig>>

interface ModalResult {
/** Whether the modal was confirmed/accepted or not */
result: boolean
/** The values entered in the modal form, if any */
values?: Record<string, FieldUpdateValue>
}

const DefaultIcons = {
[ActionType.VALIDATE]: 'PencilLine',
[ActionType.REGISTER]: 'PencilLine',
[ActionType.ARCHIVE]: 'Archive'
} as const

function QuickActionModal({
close,
config
Expand Down Expand Up @@ -130,6 +142,17 @@ function QuickActionModal({
id={`quick-action-modal-${config.label.id}`}
isOpen={true}
title={intl.formatMessage(config.label)}
titleIcon={
<Icon
color="primary"
name={
config.icon ??
(config.actionType && DefaultIcons[config.actionType]) ??
'PencilLine'
}
size="large"
/>
}
variant={'large'}
width={898}
onClose={() => close({ result: false })}
Expand All @@ -152,15 +175,25 @@ 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,
workqueue?: string
) => {
const config = quickActions[actionType]
const label = actionLabels[actionType]
const actionConfig = getActionConfig({ actionType, eventConfiguration })
const { result } = await openModal<ModalResult>((close) => (
<QuickActionModal close={close} config={{ label, ...config.modal }} />
<QuickActionModal
close={close}
config={{
label,
actionType,
icon: actionConfig?.icon,
...config.modal
}}
/>
))

// On confirmed modal, we will:
Expand Down Expand Up @@ -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
}}
/>
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,18 @@ 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.
*/
return {
modals: [assignModal, deleteModal, rejectionModal, quickActionModal],
config: {
// Core actions
[ActionType.ASSIGN]: {
label: actionLabels[ActionType.ASSIGN],
icon: 'PushPin' as const,
Expand Down Expand Up @@ -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],
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/commons/src/events/ActionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect 👍

conditionals: z
.array(ActionConditional)
.optional()
Expand Down