@palladium-ethiopia/esm-admin-app 5.4.2-pre.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.turbo/turbo-build.log +15 -0
  2. package/README.md +12 -0
  3. package/dist/117.js +1 -0
  4. package/dist/152.js +1 -0
  5. package/dist/152.js.map +1 -0
  6. package/dist/209.js +1 -0
  7. package/dist/209.js.map +1 -0
  8. package/dist/41.js +1 -0
  9. package/dist/41.js.map +1 -0
  10. package/dist/442.js +1 -0
  11. package/dist/442.js.map +1 -0
  12. package/dist/466.js +1 -0
  13. package/dist/466.js.map +1 -0
  14. package/dist/555.js +1 -0
  15. package/dist/555.js.map +1 -0
  16. package/dist/61.js +1 -0
  17. package/dist/61.js.map +1 -0
  18. package/dist/672.js +15 -0
  19. package/dist/672.js.map +1 -0
  20. package/dist/689.js +1 -0
  21. package/dist/689.js.map +1 -0
  22. package/dist/710.js +1 -0
  23. package/dist/710.js.map +1 -0
  24. package/dist/712.js +1 -0
  25. package/dist/712.js.map +1 -0
  26. package/dist/771.js +1 -0
  27. package/dist/771.js.map +1 -0
  28. package/dist/789.js +1 -0
  29. package/dist/789.js.map +1 -0
  30. package/dist/806.js +1 -0
  31. package/dist/826.js +1 -0
  32. package/dist/826.js.map +1 -0
  33. package/dist/914.js +27 -0
  34. package/dist/914.js.map +1 -0
  35. package/dist/926.js +17 -0
  36. package/dist/926.js.map +1 -0
  37. package/dist/ethiopia-esm-admin-app.js +6 -0
  38. package/dist/ethiopia-esm-admin-app.js.buildmanifest.json +556 -0
  39. package/dist/ethiopia-esm-admin-app.js.map +1 -0
  40. package/dist/main.js +32 -0
  41. package/dist/main.js.map +1 -0
  42. package/dist/routes.json +1 -0
  43. package/jest.config.js +8 -0
  44. package/package.json +52 -0
  45. package/rspack.config.js +1 -0
  46. package/src/components/confirm-modal/confirmation-operation-modal.component.tsx +43 -0
  47. package/src/components/confirm-modal/confirmation-operation.test.tsx +69 -0
  48. package/src/components/dashboard/dashboard.component.tsx +131 -0
  49. package/src/components/dashboard/dashboard.scss +38 -0
  50. package/src/components/dashboard/etl-dashboard.component.tsx +11 -0
  51. package/src/components/empty-state/empty-state-log.components.tsx +20 -0
  52. package/src/components/empty-state/empty-state-log.scss +28 -0
  53. package/src/components/empty-state/empty-state-log.test.tsx +24 -0
  54. package/src/components/facility-setup/card.component.tsx +16 -0
  55. package/src/components/facility-setup/facility-info.component.tsx +142 -0
  56. package/src/components/facility-setup/facility-info.scss +87 -0
  57. package/src/components/facility-setup/facility-setup.component.tsx +21 -0
  58. package/src/components/facility-setup/facility-setup.resource.tsx +7 -0
  59. package/src/components/facility-setup/facility-setup.scss +38 -0
  60. package/src/components/facility-setup/header/header.component.tsx +23 -0
  61. package/src/components/facility-setup/header/header.scss +19 -0
  62. package/src/components/header/header-illustration.component.tsx +13 -0
  63. package/src/components/header/header.component.tsx +28 -0
  64. package/src/components/header/header.scss +19 -0
  65. package/src/components/hook/healthWorkerAdapter.ts +213 -0
  66. package/src/components/hook/useFacilityInfo.tsx +37 -0
  67. package/src/components/hook/useSystemRoleSetting.tsx +33 -0
  68. package/src/components/locations/auto-suggest/autosuggest.component.tsx +149 -0
  69. package/src/components/locations/auto-suggest/autosuggest.scss +61 -0
  70. package/src/components/locations/auto-suggest/location-autosuggest.component.tsx +94 -0
  71. package/src/components/locations/auto-suggest/location-autosuggest.scss +48 -0
  72. package/src/components/locations/common/results-tile.component.tsx +45 -0
  73. package/src/components/locations/common/results-tile.scss +86 -0
  74. package/src/components/locations/forms/add-location/add-location.workspace.scss +34 -0
  75. package/src/components/locations/forms/add-location/add-location.workspace.tsx +200 -0
  76. package/src/components/locations/forms/search-location/search-location.workspace.scss +79 -0
  77. package/src/components/locations/forms/search-location/search-location.workspace.tsx +215 -0
  78. package/src/components/locations/header/header.component.tsx +48 -0
  79. package/src/components/locations/header/header.scss +58 -0
  80. package/src/components/locations/helpers/index.ts +16 -0
  81. package/src/components/locations/home/home-locations.component.tsx +18 -0
  82. package/src/components/locations/home/home-locations.scss +8 -0
  83. package/src/components/locations/hooks/UseFacilityLocations.ts +12 -0
  84. package/src/components/locations/hooks/useLocation.ts +18 -0
  85. package/src/components/locations/hooks/useLocationTags.ts +15 -0
  86. package/src/components/locations/tables/locations-table.component.tsx +243 -0
  87. package/src/components/locations/tables/locations-table.resource.ts +26 -0
  88. package/src/components/locations/tables/locations-table.scss +115 -0
  89. package/src/components/locations/types/index.ts +120 -0
  90. package/src/components/locations/utils/index.ts +5 -0
  91. package/src/components/logs-table/operation-log-resource.ts +41 -0
  92. package/src/components/logs-table/operation-log-table.component.tsx +120 -0
  93. package/src/components/logs-table/operation-log.scss +10 -0
  94. package/src/components/logs-table/operation-log.test.tsx +47 -0
  95. package/src/components/modal/hwr-confirmation.modal.scss +21 -0
  96. package/src/components/modal/hwr-confirmation.modal.tsx +170 -0
  97. package/src/components/modal/hwr-empty.modal.component.tsx +54 -0
  98. package/src/components/modal/hwr-sync.modal.scss +30 -0
  99. package/src/components/modal/hwr-sync.modal.tsx +209 -0
  100. package/src/components/modal/hwr-sync.resource.ts +23 -0
  101. package/src/components/provider-banner/provider-banner.component.tsx +106 -0
  102. package/src/components/provider-banner/provider-banner.module.scss +51 -0
  103. package/src/components/provider-banner/provider-banner.resource.ts +29 -0
  104. package/src/components/side-menu/left-panel.scss +42 -0
  105. package/src/components/side-menu/left-pannel.component.tsx +22 -0
  106. package/src/components/users/header/header.scss +90 -0
  107. package/src/components/users/header/user-management-header.component.tsx +42 -0
  108. package/src/components/users/manage-users/hooks/useProviderAttributeMapping.ts +110 -0
  109. package/src/components/users/manage-users/hooks/useUserFormSteps.ts +119 -0
  110. package/src/components/users/manage-users/hooks/useUserFormSubmission.ts +264 -0
  111. package/src/components/users/manage-users/hooks/useUserManagementForm.ts +122 -0
  112. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-list/user-role-scope-list.component.tsx +177 -0
  113. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-fields.scss +117 -0
  114. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope-fields.component.tsx +290 -0
  115. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +316 -0
  116. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/userRoleScopeFormSchema.tsx +43 -0
  117. package/src/components/users/manage-users/manage-user.component.tsx +19 -0
  118. package/src/components/users/manage-users/manage-user.scss +31 -0
  119. package/src/components/users/manage-users/provider-autosuggest.component.tsx +117 -0
  120. package/src/components/users/manage-users/provider-search.resource.ts +34 -0
  121. package/src/components/users/manage-users/sections/demographic-section.component.tsx +156 -0
  122. package/src/components/users/manage-users/sections/login-section.component.tsx +88 -0
  123. package/src/components/users/manage-users/sections/provider-section.component.tsx +270 -0
  124. package/src/components/users/manage-users/sections/roles-section.component.tsx +88 -0
  125. package/src/components/users/manage-users/user-details/user-detail.scss +75 -0
  126. package/src/components/users/manage-users/user-details/user-details.component.tsx +182 -0
  127. package/src/components/users/manage-users/user-list/user-list.component.tsx +378 -0
  128. package/src/components/users/manage-users/user-list/user-list.resource.ts +30 -0
  129. package/src/components/users/manage-users/user-list/user-list.scss +37 -0
  130. package/src/components/users/manage-users/user-management.constants.ts +20 -0
  131. package/src/components/users/manage-users/user-management.utils.ts +100 -0
  132. package/src/components/users/manage-users/user-management.workspace.scss +172 -0
  133. package/src/components/users/manage-users/user-management.workspace.tsx +334 -0
  134. package/src/components/users/userManagementFormSchema.tsx +179 -0
  135. package/src/config-schema.ts +142 -0
  136. package/src/constants.ts +50 -0
  137. package/src/declarations.d.ts +2 -0
  138. package/src/index.ts +55 -0
  139. package/src/left-pannel-link.component.tsx +40 -0
  140. package/src/root.component.tsx +39 -0
  141. package/src/root.scss +12 -0
  142. package/src/routes.json +100 -0
  143. package/src/setup-tests.ts +1 -0
  144. package/src/types/index.ts +385 -0
  145. package/src/user-management.resources.ts +232 -0
  146. package/src/utils/utils.ts +20 -0
  147. package/translations/am.json +159 -0
  148. package/translations/en.json +159 -0
  149. package/tsconfig.json +5 -0
@@ -0,0 +1,100 @@
1
+ import type { ProviderWithAttributes } from '../../../user-management.resources';
2
+ import type { ConfigObject } from '../../../config-schema';
3
+ import type { ProviderSearchResult } from './provider-search.resource';
4
+ import { EXCLUDED_ROLE_CATEGORY } from './user-management.constants';
5
+
6
+ export interface ProviderWithAttributesMinimal {
7
+ attributes?: Array<{ attributeType?: { uuid: string }; value?: string | { name?: string } }>;
8
+ identifier?: string;
9
+ }
10
+
11
+ export interface AttributeTypeMappingForExtraction {
12
+ licenseNumber: string;
13
+ licenseExpiry: string;
14
+ providerNationalId: string;
15
+ qualification: string;
16
+ licenseBody: string;
17
+ phoneNumber: string;
18
+ providerAddress: string;
19
+ passportNumber: string;
20
+ providerUniqueIdentifier: string;
21
+ }
22
+
23
+ export interface ProviderFormValues {
24
+ providerLicenseNumber: string;
25
+ licenseExpiryDate: Date | string;
26
+ qualification: string;
27
+ nationalId: string;
28
+ passportNumber: string;
29
+ registrationNumber: string;
30
+ phoneNumber: string;
31
+ email: string;
32
+ providerUniqueIdentifier: string;
33
+ }
34
+
35
+ export interface NameParts {
36
+ givenName: string;
37
+ middleName: string;
38
+ familyName: string;
39
+ }
40
+
41
+ export function extractNameParts(display = ''): NameParts {
42
+ const nameParts = display.split(' ');
43
+ const [givenName = '', middleName = '', familyName = ''] =
44
+ nameParts.length === 3 ? nameParts : [nameParts[0], '', nameParts[1] || ''];
45
+ return { givenName, middleName, familyName };
46
+ }
47
+
48
+ export function extractAttributeFromProvider(providerData: ProviderWithAttributes, attributeTypeUuid: string): string {
49
+ const attr = providerData.attributes?.find((a) => a.attributeType?.uuid === attributeTypeUuid);
50
+ if (!attr?.value) {
51
+ return '';
52
+ }
53
+ return typeof attr.value === 'string' ? attr.value : attr.value?.name ?? '';
54
+ }
55
+
56
+ export function hasExternalOrIhrisAttributes(
57
+ provider: ProviderWithAttributes | ProviderSearchResult,
58
+ config: Pick<ConfigObject, 'providerExternalIdAttributeTypeUuid' | 'providerIHRISIdentifierAttributeTypeUuid'>,
59
+ ): boolean {
60
+ const externalId = extractAttributeFromProvider(
61
+ provider as ProviderWithAttributes,
62
+ config.providerExternalIdAttributeTypeUuid,
63
+ );
64
+ const ihrisIdentifier = extractAttributeFromProvider(
65
+ provider as ProviderWithAttributes,
66
+ config.providerIHRISIdentifierAttributeTypeUuid,
67
+ );
68
+ return !!(externalId || ihrisIdentifier);
69
+ }
70
+
71
+ export function extractProviderFormValues(
72
+ provider: ProviderWithAttributesMinimal | ProviderWithAttributes,
73
+ attributeTypeMapping: AttributeTypeMappingForExtraction,
74
+ ): ProviderFormValues {
75
+ const getVal = (uuid: string) => extractAttributeFromProvider(provider as ProviderWithAttributes, uuid);
76
+ const licenseExpiryVal = getVal(attributeTypeMapping.licenseExpiry);
77
+ return {
78
+ providerLicenseNumber: getVal(attributeTypeMapping.licenseNumber),
79
+ licenseExpiryDate: licenseExpiryVal ? new Date(licenseExpiryVal) : '',
80
+ qualification: getVal(attributeTypeMapping.qualification),
81
+ nationalId: getVal(attributeTypeMapping.providerNationalId),
82
+ passportNumber: getVal(attributeTypeMapping.passportNumber),
83
+ registrationNumber: getVal(attributeTypeMapping.licenseBody),
84
+ phoneNumber: getVal(attributeTypeMapping.phoneNumber),
85
+ email: getVal(attributeTypeMapping.providerAddress),
86
+ providerUniqueIdentifier: getVal(attributeTypeMapping.providerUniqueIdentifier),
87
+ };
88
+ }
89
+
90
+ export interface RoleConfigCategory {
91
+ category: string;
92
+ roles: string[];
93
+ }
94
+
95
+ export function filterRolesConfig(
96
+ rolesConfig: RoleConfigCategory[],
97
+ excludeCategory = EXCLUDED_ROLE_CATEGORY,
98
+ ): RoleConfigCategory[] {
99
+ return rolesConfig.filter((category) => category.category !== excludeCategory);
100
+ }
@@ -0,0 +1,172 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .form {
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: space-between;
9
+ height: 100%;
10
+ }
11
+
12
+ .formContainer {
13
+ margin-right: layout.$spacing-05;
14
+ margin-left: 0;
15
+ margin-top: layout.$spacing-05;
16
+ margin-bottom: layout.$spacing-05;
17
+ }
18
+
19
+ .tablet {
20
+ padding: layout.$spacing-06 layout.$spacing-05;
21
+ background-color: colors.$white;
22
+ }
23
+
24
+ .desktop {
25
+ padding: 0;
26
+ width: 100%;
27
+ }
28
+
29
+ .formStackControl {
30
+ row-gap: layout.$layout-01;
31
+ }
32
+
33
+ .checkBoxColumn {
34
+ background-color: colors.$gray-10;
35
+ margin: layout.$spacing-05;
36
+ padding: layout.$spacing-03;
37
+ border-radius: layout.$spacing-05;
38
+ }
39
+
40
+ .checkboxLabelSingleLine {
41
+ white-space: nowrap;
42
+ overflow: hidden;
43
+ text-overflow: ellipsis;
44
+ display: inline-block;
45
+ font-size: 14px;
46
+ }
47
+
48
+ .checkboxGroupGrid {
49
+ display: grid;
50
+ grid-template-columns: repeat(2, 1fr);
51
+ row-gap: layout.$spacing-03;
52
+ column-gap: layout.$spacing-02;
53
+ font-size: 14px;
54
+
55
+ :global(.cds--fieldset-legend) {
56
+ font-size: 14px;
57
+ }
58
+ }
59
+
60
+ .CheckboxGroup .bx--fieldset-legend {
61
+ font-size: 14px;
62
+ }
63
+
64
+ .btn {
65
+ min-width: 50%;
66
+ }
67
+
68
+ .checkboxLabelSelected {
69
+ background-color: colors.$green-10;
70
+ border-radius: layout.$spacing-05;
71
+ padding: layout.$spacing-02;
72
+ display: flex;
73
+ align-items: center;
74
+ font-size: 14px;
75
+ transition: background-color 0.3s ease;
76
+ }
77
+
78
+ .checkboxLabel {
79
+ background-color: transparent;
80
+ border-radius: layout.$spacing-05;
81
+ padding: layout.$spacing-02;
82
+ display: flex;
83
+ align-items: center;
84
+ transition: background-color 0.3s ease;
85
+ font-size: 14px;
86
+ }
87
+
88
+ .leftContainer {
89
+ display: flex;
90
+ flex-direction: column;
91
+ border-radius: layout.$spacing-05;
92
+ overflow: hidden;
93
+ }
94
+
95
+ .leftLayout {
96
+ display: flex;
97
+ flex-direction: row;
98
+ height: 100%;
99
+ }
100
+
101
+ .progressIndicator {
102
+ display: flex;
103
+ flex-direction: column;
104
+ min-width: 200px;
105
+ padding-top: layout.$spacing-03;
106
+ border-right: 1px solid colors.$gray-30;
107
+ }
108
+
109
+ .sections {
110
+ flex: 1;
111
+ padding: layout.$spacing-04;
112
+ }
113
+
114
+ @media (max-width: 768px) {
115
+ .leftLayout {
116
+ flex-direction: column;
117
+ }
118
+
119
+ .progressIndicator {
120
+ flex-direction: row;
121
+ border-right: none;
122
+ border-bottom: 1px solid colors.$gray-30;
123
+ min-width: unset;
124
+ justify-content: space-between;
125
+ padding: layout.$spacing-02;
126
+ }
127
+
128
+ .sections {
129
+ padding: layout.$spacing-03;
130
+ }
131
+ }
132
+
133
+ .multilineCheckboxLabel .cds--checkbox-label {
134
+ display: block;
135
+ white-space: normal;
136
+ word-wrap: break-word;
137
+ line-height: 1.4;
138
+ }
139
+
140
+ .formHeaderSection {
141
+ @include type.type-style('heading-02');
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: space-between;
145
+ row-gap: layout.$spacing-06;
146
+ position: relative;
147
+ padding-top: layout.$spacing-06;
148
+
149
+ & > span {
150
+ @include type.type-style('body-01');
151
+ }
152
+ }
153
+ .formIdentifierType {
154
+ color: colors.$gray-70;
155
+ display: inline-block;
156
+ font-size: layout.$spacing-04;
157
+ line-height: layout.$spacing-05;
158
+ line-height: layout.$spacing-05;
159
+ margin-bottom: layout.$spacing-03;
160
+ vertical-align: baseline;
161
+ }
162
+ .formRow {
163
+ display: flex;
164
+ align-items: center;
165
+ }
166
+ .roleStockFields {
167
+ display: flex;
168
+ flex-direction: column;
169
+ gap: layout.$spacing-05;
170
+ outline: 1px solid colors.$gray-10;
171
+ padding: layout.$spacing-05;
172
+ }
@@ -0,0 +1,334 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { DefaultWorkspaceProps, useLayoutType, useConfig, parseDate } from '@openmrs/esm-framework';
4
+ import { FormProvider } from 'react-hook-form';
5
+ import styles from './user-management.workspace.scss';
6
+ import { ButtonSet, Button, InlineLoading, Stack, ProgressIndicator, ProgressStep } from '@carbon/react';
7
+ import classNames from 'classnames';
8
+ import {
9
+ useRoles,
10
+ useProvider,
11
+ useProviderAttributeType,
12
+ type ProviderWithAttributes,
13
+ } from '../../../user-management.resources';
14
+ import { useSystemUserRoleConfigSetting } from '../../hook/useSystemRoleSetting';
15
+ import { User } from '../../../types';
16
+ import { ConfigObject } from '../../../config-schema';
17
+ import { useUsers } from './user-list/user-list.resource';
18
+ import { type ProviderSearchResult } from './provider-search.resource';
19
+ import { extractNameParts, extractProviderFormValues, hasExternalOrIhrisAttributes } from './user-management.utils';
20
+ import { useProviderAttributeMapping } from './hooks/useProviderAttributeMapping';
21
+ import { useUserManagementForm, type UserFormSchema } from './hooks/useUserManagementForm';
22
+ import { useUserFormSteps } from './hooks/useUserFormSteps';
23
+ import { useUserFormSubmission } from './hooks/useUserFormSubmission';
24
+ import { DemographicSection } from './sections/demographic-section.component';
25
+ import { ProviderSection } from './sections/provider-section.component';
26
+ import { LoginSection } from './sections/login-section.component';
27
+ import { RolesSection } from './sections/roles-section.component';
28
+
29
+ type ManageUserWorkspaceProps = DefaultWorkspaceProps & {
30
+ initialUserValue?: User;
31
+ initialProvider?: ProviderSearchResult | ProviderWithAttributes;
32
+ };
33
+
34
+ const EMPTY_FORM_VALUES: Partial<UserFormSchema> = {
35
+ givenName: '',
36
+ middleName: '',
37
+ familyName: '',
38
+ gender: undefined,
39
+ phoneNumber: '',
40
+ email: '',
41
+ providerIdentifiers: false,
42
+ username: '',
43
+ password: '',
44
+ confirmPassword: '',
45
+ roles: [],
46
+ providerUniqueIdentifier: '',
47
+ nationalId: '',
48
+ passportNumber: '',
49
+ providerLicense: '',
50
+ registrationNumber: '',
51
+ qualification: '',
52
+ licenseExpiryDate: undefined,
53
+ systemId: '',
54
+ isEditProvider: false,
55
+ };
56
+
57
+ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
58
+ closeWorkspace,
59
+ promptBeforeClosing,
60
+ closeWorkspaceWithSavedChanges,
61
+ initialUserValue = {} as User,
62
+ initialProvider,
63
+ }) => {
64
+ const { t } = useTranslation();
65
+ const isTablet = useLayoutType() === 'tablet';
66
+ const [selectedProvider, setSelectedProvider] = useState<ProviderSearchResult | null>(null);
67
+ const [formResetKey, setFormResetKey] = useState(0);
68
+
69
+ // Gets providers by name and returns all rows if there is no match
70
+ const { provider = [], loadingProvider, providerError } = useProvider(initialUserValue?.person?.display);
71
+
72
+ const providerSource: ProviderWithAttributes[] = selectedProvider
73
+ ? [selectedProvider as ProviderWithAttributes]
74
+ : initialProvider
75
+ ? [initialProvider as ProviderWithAttributes]
76
+ : provider;
77
+
78
+ const { users } = useUsers();
79
+ const usernames =
80
+ users?.map((user) => user.username).filter((username) => username !== initialUserValue?.username) || [];
81
+ const isInitialValuesEmpty = Object.keys(initialUserValue).length === 0;
82
+
83
+ const { providerAttributeType = [] } = useProviderAttributeType();
84
+ const { roles = [], isLoading } = useRoles();
85
+ const { rolesConfig } = useSystemUserRoleConfigSetting();
86
+ const config = useConfig<ConfigObject>();
87
+
88
+ const isProviderReadOnly = providerSource.length > 0 && hasExternalOrIhrisAttributes(providerSource[0], config);
89
+
90
+ const { attributeTypeMapping, providerValues } = useProviderAttributeMapping({
91
+ provider: providerSource,
92
+ providerAttributeType,
93
+ config,
94
+ });
95
+
96
+ const { userFormMethods, errors, isSubmitting, isDirty } = useUserManagementForm({
97
+ initialUserValue,
98
+ usernames,
99
+ providerValues,
100
+ loadingProvider,
101
+ isInitialValuesEmpty,
102
+ isProviderReadOnly,
103
+ });
104
+
105
+ const { setValue, trigger } = userFormMethods;
106
+
107
+ const isStepValid = useCallback(
108
+ (stepIndex: number) => {
109
+ switch (stepIndex) {
110
+ case 0:
111
+ return !errors.givenName && !errors.familyName && !errors.gender && !errors.phoneNumber && !errors.email;
112
+ case 1:
113
+ return !errors.username && !errors.password && !errors.confirmPassword;
114
+ case 2:
115
+ case 3:
116
+ default:
117
+ return true;
118
+ }
119
+ },
120
+ [
121
+ errors.givenName,
122
+ errors.familyName,
123
+ errors.gender,
124
+ errors.phoneNumber,
125
+ errors.email,
126
+ errors.username,
127
+ errors.password,
128
+ errors.confirmPassword,
129
+ ],
130
+ );
131
+
132
+ const {
133
+ steps,
134
+ currentIndex,
135
+ hasDemographicInfo,
136
+ hasLoginInfo,
137
+ hasProviderAccount,
138
+ hasRoles,
139
+ markStepComplete,
140
+ isStepEnabled,
141
+ handleBackClick,
142
+ handleNextClick,
143
+ handleStepChange,
144
+ getSubmitButtonText,
145
+ getSubmitButtonType,
146
+ getSubmitButtonIcon,
147
+ } = useUserFormSteps({ t, closeWorkspace, isInitialValuesEmpty, isStepValid });
148
+
149
+ const handleNextWithValidation = useCallback(
150
+ async (e: React.MouseEvent) => {
151
+ let isValid = true;
152
+ if (hasDemographicInfo) {
153
+ isValid = await trigger(['givenName', 'familyName', 'gender', 'phoneNumber', 'email']);
154
+ } else if (hasLoginInfo) {
155
+ isValid = await trigger(['username', 'password', 'confirmPassword']);
156
+ }
157
+ if (!isValid) {
158
+ return;
159
+ }
160
+ markStepComplete(currentIndex);
161
+ handleNextClick(e);
162
+ },
163
+ [hasDemographicInfo, hasLoginInfo, currentIndex, trigger, markStepComplete, handleNextClick],
164
+ );
165
+
166
+ const { onSubmit, handleError } = useUserFormSubmission({
167
+ attributeTypeMapping,
168
+ selectedProvider,
169
+ initialUserValue,
170
+ provider: providerSource,
171
+ providerValues,
172
+ isProviderReadOnly,
173
+ isInitialValuesEmpty,
174
+ personPhonenumberAttributeUuid: config.personPhonenumberAttributeUuid,
175
+ personEmailAttributeUuid: config.personEmailAttributeUuid,
176
+ licenseBodyUuid: config.licenseBodyUuid,
177
+ providerNationalIdUuid: config.providerNationalIdUuid,
178
+ licenseNumberUuid: config.licenseNumberUuid,
179
+ passportNumberUuid: config.passportNumberUuid,
180
+ providerUniqueIdentifierAttributeTypeUuid: config.providerUniqueIdentifierAttributeTypeUuid,
181
+ closeWorkspaceWithSavedChanges,
182
+ t,
183
+ });
184
+
185
+ useEffect(() => {
186
+ if (isDirty) {
187
+ promptBeforeClosing(() => isDirty);
188
+ }
189
+ }, [isDirty, promptBeforeClosing]);
190
+
191
+ const setPersonValuesFromProvider = useCallback(
192
+ (providerData: ProviderSearchResult) => {
193
+ const display = providerData.person?.display ?? '';
194
+ const { givenName, middleName, familyName } = extractNameParts(display);
195
+ setValue('givenName', givenName, { shouldDirty: true });
196
+ setValue('middleName', middleName, { shouldDirty: true });
197
+ setValue('familyName', familyName, { shouldDirty: true });
198
+ setValue('gender', (providerData.person?.gender as 'M' | 'F') || undefined, { shouldDirty: true });
199
+ setSelectedProvider(providerData);
200
+ },
201
+ [setValue],
202
+ );
203
+
204
+ const setProviderValuesFromProvider = useCallback(
205
+ (providerData: ProviderSearchResult) => {
206
+ const values = extractProviderFormValues(providerData, attributeTypeMapping);
207
+ setValue('providerUniqueIdentifier', values.providerUniqueIdentifier, { shouldDirty: true });
208
+ setValue('nationalId', values.nationalId, { shouldDirty: true });
209
+ setValue('passportNumber', values.passportNumber, { shouldDirty: true });
210
+ setValue('providerLicense', values.providerLicenseNumber, { shouldDirty: true });
211
+ setValue('registrationNumber', values.registrationNumber, { shouldDirty: true });
212
+ setValue('qualification', values.qualification, { shouldDirty: true });
213
+ setValue(
214
+ 'licenseExpiryDate',
215
+ values.licenseExpiryDate
216
+ ? values.licenseExpiryDate instanceof Date
217
+ ? values.licenseExpiryDate
218
+ : parseDate(values.licenseExpiryDate)
219
+ : undefined,
220
+ { shouldDirty: true },
221
+ );
222
+ setValue('phoneNumber', values.phoneNumber, { shouldDirty: true });
223
+ setValue('email', values.email, { shouldDirty: true });
224
+ if (providerData.identifier) {
225
+ setValue('systemId', providerData.identifier, { shouldDirty: true });
226
+ }
227
+ },
228
+ [setValue, attributeTypeMapping],
229
+ );
230
+
231
+ const handleProviderSelected = useCallback(
232
+ async (providerData: ProviderSearchResult) => {
233
+ setPersonValuesFromProvider(providerData);
234
+ setProviderValuesFromProvider(providerData);
235
+ await trigger(['givenName', 'familyName', 'gender', 'phoneNumber', 'email']);
236
+ },
237
+ [setPersonValuesFromProvider, setProviderValuesFromProvider, trigger],
238
+ );
239
+
240
+ const handleFormReset = useCallback(() => {
241
+ setSelectedProvider(null);
242
+ userFormMethods.reset(EMPTY_FORM_VALUES, { keepDefaultValues: false });
243
+ setFormResetKey((k) => k + 1);
244
+ }, [userFormMethods]);
245
+
246
+ return (
247
+ <div className={styles.leftContainer}>
248
+ <div>
249
+ <div className={styles.leftLayout}>
250
+ <ProgressIndicator
251
+ currentIndex={currentIndex}
252
+ spaceEqually={true}
253
+ vertical={true}
254
+ className={styles.progressIndicator}
255
+ onChange={handleStepChange}>
256
+ {steps.map((step, index) => (
257
+ <ProgressStep
258
+ key={step.id}
259
+ label={step.label}
260
+ className={styles.ProgresStep}
261
+ disabled={!isStepEnabled(index)}
262
+ />
263
+ ))}
264
+ </ProgressIndicator>
265
+ <div className={styles.sections}>
266
+ <FormProvider {...userFormMethods}>
267
+ <form onSubmit={userFormMethods.handleSubmit(onSubmit, handleError)} className={styles.form}>
268
+ <div className={styles.formContainer}>
269
+ <Stack className={styles.formStackControl} gap={7}>
270
+ {hasDemographicInfo && (
271
+ <DemographicSection
272
+ control={userFormMethods.control}
273
+ errors={errors}
274
+ isInitialValuesEmpty={isInitialValuesEmpty}
275
+ isProviderReadOnly={isProviderReadOnly}
276
+ onProviderSelected={handleProviderSelected}
277
+ onFormReset={handleFormReset}
278
+ showResetButton={isInitialValuesEmpty}
279
+ formResetKey={formResetKey}
280
+ isFormDirty={isDirty}
281
+ />
282
+ )}
283
+ {hasProviderAccount && (
284
+ <ProviderSection
285
+ control={userFormMethods.control}
286
+ errors={errors}
287
+ isInitialValuesEmpty={isInitialValuesEmpty}
288
+ isProviderReadOnly={isProviderReadOnly}
289
+ loadingProvider={initialProvider ? false : loadingProvider}
290
+ providerError={providerError}
291
+ provider={providerSource}
292
+ />
293
+ )}
294
+ {hasLoginInfo && <LoginSection control={userFormMethods.control} errors={errors} />}
295
+ {hasRoles && (
296
+ <RolesSection
297
+ control={userFormMethods.control}
298
+ roles={roles}
299
+ rolesConfig={rolesConfig || []}
300
+ isLoading={isLoading}
301
+ />
302
+ )}
303
+ </Stack>
304
+ </div>
305
+ <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
306
+ <Button kind="secondary" onClick={handleBackClick} className={styles.btn}>
307
+ {t(hasDemographicInfo ? 'cancel' : 'back', hasDemographicInfo ? 'Cancel' : 'Back')}
308
+ </Button>
309
+ <Button
310
+ kind="primary"
311
+ type={getSubmitButtonType()}
312
+ disabled={isSubmitting || Object.keys(errors).length > 0}
313
+ renderIcon={getSubmitButtonIcon()}
314
+ className={styles.btn}
315
+ onClick={handleNextWithValidation}>
316
+ {isSubmitting ? (
317
+ <span style={{ display: 'flex', alignItems: 'center' }}>
318
+ {t('submitting', 'Submitting...')} <InlineLoading status="active" />
319
+ </span>
320
+ ) : (
321
+ getSubmitButtonText()
322
+ )}
323
+ </Button>
324
+ </ButtonSet>
325
+ </form>
326
+ </FormProvider>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ );
332
+ };
333
+
334
+ export default ManageUserWorkspace;