Skip to content

Commit 295c3da

Browse files
Nil20Nil20Zangetsu101
authored
feat: add icon in action config (#11251)
* feat: add icon in action config * fix: make icon optional * chore: use icon for custom action * chore: use icon for all configurable actions * fix: group core and configurable actions for action menu * feat: use icon in quick action modal * chore: use icon for custom action modal * fix: amend icon size * simplify default icon condition Co-authored-by: Tameem Bin Haider <[email protected]> * fix: use reusable func --------- Co-authored-by: Nil20 <[email protected]> Co-authored-by: Tameem Bin Haider <[email protected]>
1 parent 5785601 commit 295c3da

File tree

3 files changed

+87
-42
lines changed

3 files changed

+87
-42
lines changed

packages/client/src/v2-events/features/events/actions/quick-actions/useQuickActionModal.tsx

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ import {
1818
PrimaryButton,
1919
TertiaryButton
2020
} from '@opencrvs/components/lib/buttons'
21+
import { Icon, IconProps } from '@opencrvs/components/src/Icon'
2122
import {
2223
ActionType,
2324
CustomActionConfig,
25+
EventConfig,
2426
EventIndex,
2527
FieldConfig,
2628
FieldUpdateValue,
29+
getActionConfig,
2730
runFieldValidations
2831
} from '@opencrvs/commons/client'
2932
import { buttonMessages } from '@client/i18n/messages'
@@ -34,16 +37,25 @@ import { useUserAllowedActions } from '../../../workqueues/EventOverview/compone
3437
import { useModal } from '../../../../hooks/useModal'
3538
import { useEvents } from '../../useEvents/useEvents'
3639
import { actionLabels } from '../../../workqueues/EventOverview/components/useAllowedActionConfigurations'
40+
import { useEventConfiguration } from '../../useEventConfiguration'
3741
import { validate } from './validate'
3842
import { register } from './register'
3943
import { archive } from './archive'
4044

45+
const quickActions = {
46+
[ActionType.VALIDATE]: validate,
47+
[ActionType.REGISTER]: register,
48+
[ActionType.ARCHIVE]: archive
49+
} as const satisfies Partial<Record<ActionType, QuickActionConfig>>
50+
4151
interface ModalConfig {
4252
label?: MessageDescriptor
4353
description?: MessageDescriptor
4454
confirmButtonType?: 'primary' | 'danger'
4555
confirmButtonLabel?: MessageDescriptor
4656
fields?: FieldConfig[]
57+
actionType?: keyof typeof quickActions
58+
icon?: IconProps['name']
4759
}
4860

4961
export interface QuickActionConfig {
@@ -61,19 +73,19 @@ export interface QuickActionConfig {
6173
}) => void | Promise<void>
6274
}
6375

64-
const quickActions = {
65-
[ActionType.VALIDATE]: validate,
66-
[ActionType.REGISTER]: register,
67-
[ActionType.ARCHIVE]: archive
68-
} as const satisfies Partial<Record<ActionType, QuickActionConfig>>
69-
7076
interface ModalResult {
7177
/** Whether the modal was confirmed/accepted or not */
7278
result: boolean
7379
/** The values entered in the modal form, if any */
7480
values?: Record<string, FieldUpdateValue>
7581
}
7682

83+
const DefaultIcons = {
84+
[ActionType.VALIDATE]: 'PencilLine',
85+
[ActionType.REGISTER]: 'PencilLine',
86+
[ActionType.ARCHIVE]: 'Archive'
87+
} as const
88+
7789
function QuickActionModal({
7890
close,
7991
config
@@ -130,6 +142,17 @@ function QuickActionModal({
130142
id={`quick-action-modal-${config.label.id}`}
131143
isOpen={true}
132144
title={intl.formatMessage(config.label)}
145+
titleIcon={
146+
<Icon
147+
color="primary"
148+
name={
149+
config.icon ??
150+
(config.actionType && DefaultIcons[config.actionType]) ??
151+
'PencilLine'
152+
}
153+
size="large"
154+
/>
155+
}
133156
variant={'large'}
134157
width={898}
135158
onClose={() => close({ result: false })}
@@ -152,15 +175,25 @@ export function useQuickActionModal(event: EventIndex) {
152175
const navigate = useNavigate()
153176
const { actions, customActions } = useEvents()
154177
const { isActionAllowed } = useUserAllowedActions(event.type)
178+
const { eventConfiguration } = useEventConfiguration(event.type)
155179

156180
const onQuickAction = async (
157181
actionType: keyof typeof quickActions,
158182
workqueue?: string
159183
) => {
160184
const config = quickActions[actionType]
161185
const label = actionLabels[actionType]
186+
const actionConfig = getActionConfig({ actionType, eventConfiguration })
162187
const { result } = await openModal<ModalResult>((close) => (
163-
<QuickActionModal close={close} config={{ label, ...config.modal }} />
188+
<QuickActionModal
189+
close={close}
190+
config={{
191+
label,
192+
actionType,
193+
icon: actionConfig?.icon,
194+
...config.modal
195+
}}
196+
/>
164197
))
165198

166199
// On confirmed modal, we will:
@@ -210,7 +243,8 @@ export function useCustomActionModal(event: EventIndex) {
210243
...customActionConfigBase,
211244
label: actionConfig.label,
212245
description: actionConfig.supportingCopy,
213-
fields: actionConfig.form
246+
fields: actionConfig.form,
247+
icon: actionConfig.icon
214248
}}
215249
/>
216250
))

packages/client/src/v2-events/features/workqueues/EventOverview/components/useAllowedActionConfigurations.tsx

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,18 @@ function useViewableActionConfigurations(
322322
: actionLabels[ActionType.VALIDATE]
323323
}
324324

325+
const getAction = (type: ActionType) => {
326+
return eventConfiguration.actions.find((action) => action.type === type)
327+
}
328+
325329
/**
326330
* Configuration should be kept simple. Actions should do one thing, or navigate to one place.
327331
* If you need to extend the functionality, consider whether it can be done elsewhere.
328332
*/
329333
return {
330334
modals: [assignModal, deleteModal, rejectionModal, quickActionModal],
331335
config: {
336+
// Core actions
332337
[ActionType.ASSIGN]: {
333338
label: actionLabels[ActionType.ASSIGN],
334339
icon: 'PushPin' as const,
@@ -373,8 +378,36 @@ function useViewableActionConfigurations(
373378
},
374379
disabled: !isOnline
375380
},
376-
[ActionType.DECLARE]: {
381+
[ActionType.ARCHIVE]: {
382+
label: actionLabels[ActionType.ARCHIVE],
383+
icon: 'Archive' as const,
384+
onClick: async (workqueue) =>
385+
onQuickAction(ActionType.ARCHIVE, workqueue),
386+
disabled: !isDownloadedAndAssignedToUser
387+
},
388+
[ActionType.MARK_AS_DUPLICATE]: {
389+
label: actionLabels[ActionType.MARK_AS_DUPLICATE],
377390
icon: 'PencilLine' as const,
391+
onClick: (workqueue?: string) => {
392+
clearEphemeralFormState()
393+
return navigate(
394+
ROUTES.V2.EVENTS.REVIEW_POTENTIAL_DUPLICATE.buildPath(
395+
{ eventId },
396+
{ workqueue }
397+
)
398+
)
399+
},
400+
disabled: !isDownloadedAndAssignedToUser || isAssignmentInProgress
401+
},
402+
[ActionType.DELETE]: {
403+
label: actionLabels[ActionType.DELETE],
404+
icon: 'Trash' as const,
405+
onClick: onDelete,
406+
disabled: !isDownloadedAndAssignedToUser
407+
},
408+
// Configurable event actions
409+
[ActionType.DECLARE]: {
410+
icon: getAction(ActionType.DECLARE)?.icon ?? ('PencilLine' as const),
378411
label: isReviewingDeclaration
379412
? reviewLabel
380413
: actionLabels[ActionType.DECLARE],
@@ -392,7 +425,7 @@ function useViewableActionConfigurations(
392425
},
393426
[ActionType.REJECT]: {
394427
label: actionLabels[ActionType.REJECT],
395-
icon: 'FileX',
428+
icon: getAction(ActionType.REJECT)?.icon ?? 'FileX',
396429
onClick: async (workqueue) =>
397430
handleRejection(() =>
398431
workqueue
@@ -406,7 +439,7 @@ function useViewableActionConfigurations(
406439
},
407440
[ActionType.VALIDATE]: {
408441
label: resolveValidateLabel(),
409-
icon: 'PencilLine' as const,
442+
icon: getAction(ActionType.VALIDATE)?.icon ?? ('PencilLine' as const),
410443
onClick: async (workqueue) => {
411444
if (userMayRegister) {
412445
return onQuickAction(ActionType.REGISTER, workqueue)
@@ -421,18 +454,11 @@ function useViewableActionConfigurations(
421454
ctaLabel: reviewLabel,
422455
disabled: !isDownloadedAndAssignedToUser
423456
},
424-
[ActionType.ARCHIVE]: {
425-
label: actionLabels[ActionType.ARCHIVE],
426-
icon: 'Archive' as const,
427-
onClick: async (workqueue) =>
428-
onQuickAction(ActionType.ARCHIVE, workqueue),
429-
disabled: !isDownloadedAndAssignedToUser
430-
},
431457
[ActionType.REGISTER]: {
432458
label: isReviewingIncompleteDeclaration
433459
? reviewLabel
434460
: actionLabels[ActionType.REGISTER],
435-
icon: 'PencilLine' as const,
461+
icon: getAction(ActionType.REGISTER)?.icon ?? ('PencilLine' as const),
436462
onClick: async (workqueue) =>
437463
onQuickAction(ActionType.REGISTER, workqueue),
438464
ctaLabel: reviewLabel,
@@ -443,23 +469,10 @@ function useViewableActionConfigurations(
443469
disabled: !isDownloadedAndAssignedToUser,
444470
hidden: !event.flags.includes('validated')
445471
},
446-
[ActionType.MARK_AS_DUPLICATE]: {
447-
label: actionLabels[ActionType.MARK_AS_DUPLICATE],
448-
icon: 'PencilLine' as const,
449-
onClick: (workqueue?: string) => {
450-
clearEphemeralFormState()
451-
return navigate(
452-
ROUTES.V2.EVENTS.REVIEW_POTENTIAL_DUPLICATE.buildPath(
453-
{ eventId },
454-
{ workqueue }
455-
)
456-
)
457-
},
458-
disabled: !isDownloadedAndAssignedToUser || isAssignmentInProgress
459-
},
460472
[ActionType.PRINT_CERTIFICATE]: {
461473
label: actionLabels[ActionType.PRINT_CERTIFICATE],
462-
icon: 'Printer' as const,
474+
icon:
475+
getAction(ActionType.PRINT_CERTIFICATE)?.icon ?? ('Printer' as const),
463476
onClick: (workqueue?: string) => {
464477
clearEphemeralFormState()
465478
return navigate(
@@ -472,15 +485,11 @@ function useViewableActionConfigurations(
472485
disabled: !isDownloadedAndAssignedToUser || eventIsWaitingForCorrection,
473486
hidden: eventIsWaitingForCorrection
474487
},
475-
[ActionType.DELETE]: {
476-
label: actionLabels[ActionType.DELETE],
477-
icon: 'Trash' as const,
478-
onClick: onDelete,
479-
disabled: !isDownloadedAndAssignedToUser
480-
},
481488
[ActionType.REQUEST_CORRECTION]: {
482489
label: actionLabels[ActionType.REQUEST_CORRECTION],
483-
icon: 'NotePencil' as const,
490+
icon:
491+
getAction(ActionType.REQUEST_CORRECTION)?.icon ??
492+
('NotePencil' as const),
484493
onClick: (workqueue?: string) => {
485494
const correctionPages = eventConfiguration.actions.find(
486495
(action) => action.type === ActionType.REQUEST_CORRECTION
@@ -586,7 +595,7 @@ function useCustomActionConfigs(
586595
)
587596
.map((action) => ({
588597
label: action.label,
589-
icon: 'PencilLine' as const,
598+
icon: action.icon ?? ('PencilLine' as const),
590599
onClick: async (workqueue?: string) =>
591600
onCustomAction(action, workqueue),
592601
disabled: !isDownloadedAndAssignedToUser,

packages/commons/src/events/ActionConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ActionFormConfig } from './FormConfig'
1616
import { DeduplicationConfig } from './DeduplicationConfig'
1717
import { ActionFlagConfig } from './Flag'
1818
import { ActionConditional } from './Conditional'
19+
import { AvailableIcons } from '../icons'
1920

2021
export const DeclarationReviewConfig = z
2122
.object({
@@ -41,6 +42,7 @@ export const ActionConfigBase = z.object({
4142
supportingCopy: TranslationConfig.optional().describe(
4243
'Text displayed on the confirmation'
4344
),
45+
icon: AvailableIcons.describe('Icon representing the action').optional(),
4446
conditionals: z
4547
.array(ActionConditional)
4648
.optional()

0 commit comments

Comments
 (0)