@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.
- package/.turbo/turbo-build.log +15 -0
- package/README.md +12 -0
- package/dist/117.js +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/209.js +1 -0
- package/dist/209.js.map +1 -0
- package/dist/41.js +1 -0
- package/dist/41.js.map +1 -0
- package/dist/442.js +1 -0
- package/dist/442.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/555.js +1 -0
- package/dist/555.js.map +1 -0
- package/dist/61.js +1 -0
- package/dist/61.js.map +1 -0
- package/dist/672.js +15 -0
- package/dist/672.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/710.js +1 -0
- package/dist/710.js.map +1 -0
- package/dist/712.js +1 -0
- package/dist/712.js.map +1 -0
- package/dist/771.js +1 -0
- package/dist/771.js.map +1 -0
- package/dist/789.js +1 -0
- package/dist/789.js.map +1 -0
- package/dist/806.js +1 -0
- package/dist/826.js +1 -0
- package/dist/826.js.map +1 -0
- package/dist/914.js +27 -0
- package/dist/914.js.map +1 -0
- package/dist/926.js +17 -0
- package/dist/926.js.map +1 -0
- package/dist/ethiopia-esm-admin-app.js +6 -0
- package/dist/ethiopia-esm-admin-app.js.buildmanifest.json +556 -0
- package/dist/ethiopia-esm-admin-app.js.map +1 -0
- package/dist/main.js +32 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +8 -0
- package/package.json +52 -0
- package/rspack.config.js +1 -0
- package/src/components/confirm-modal/confirmation-operation-modal.component.tsx +43 -0
- package/src/components/confirm-modal/confirmation-operation.test.tsx +69 -0
- package/src/components/dashboard/dashboard.component.tsx +131 -0
- package/src/components/dashboard/dashboard.scss +38 -0
- package/src/components/dashboard/etl-dashboard.component.tsx +11 -0
- package/src/components/empty-state/empty-state-log.components.tsx +20 -0
- package/src/components/empty-state/empty-state-log.scss +28 -0
- package/src/components/empty-state/empty-state-log.test.tsx +24 -0
- package/src/components/facility-setup/card.component.tsx +16 -0
- package/src/components/facility-setup/facility-info.component.tsx +142 -0
- package/src/components/facility-setup/facility-info.scss +87 -0
- package/src/components/facility-setup/facility-setup.component.tsx +21 -0
- package/src/components/facility-setup/facility-setup.resource.tsx +7 -0
- package/src/components/facility-setup/facility-setup.scss +38 -0
- package/src/components/facility-setup/header/header.component.tsx +23 -0
- package/src/components/facility-setup/header/header.scss +19 -0
- package/src/components/header/header-illustration.component.tsx +13 -0
- package/src/components/header/header.component.tsx +28 -0
- package/src/components/header/header.scss +19 -0
- package/src/components/hook/healthWorkerAdapter.ts +213 -0
- package/src/components/hook/useFacilityInfo.tsx +37 -0
- package/src/components/hook/useSystemRoleSetting.tsx +33 -0
- package/src/components/locations/auto-suggest/autosuggest.component.tsx +149 -0
- package/src/components/locations/auto-suggest/autosuggest.scss +61 -0
- package/src/components/locations/auto-suggest/location-autosuggest.component.tsx +94 -0
- package/src/components/locations/auto-suggest/location-autosuggest.scss +48 -0
- package/src/components/locations/common/results-tile.component.tsx +45 -0
- package/src/components/locations/common/results-tile.scss +86 -0
- package/src/components/locations/forms/add-location/add-location.workspace.scss +34 -0
- package/src/components/locations/forms/add-location/add-location.workspace.tsx +200 -0
- package/src/components/locations/forms/search-location/search-location.workspace.scss +79 -0
- package/src/components/locations/forms/search-location/search-location.workspace.tsx +215 -0
- package/src/components/locations/header/header.component.tsx +48 -0
- package/src/components/locations/header/header.scss +58 -0
- package/src/components/locations/helpers/index.ts +16 -0
- package/src/components/locations/home/home-locations.component.tsx +18 -0
- package/src/components/locations/home/home-locations.scss +8 -0
- package/src/components/locations/hooks/UseFacilityLocations.ts +12 -0
- package/src/components/locations/hooks/useLocation.ts +18 -0
- package/src/components/locations/hooks/useLocationTags.ts +15 -0
- package/src/components/locations/tables/locations-table.component.tsx +243 -0
- package/src/components/locations/tables/locations-table.resource.ts +26 -0
- package/src/components/locations/tables/locations-table.scss +115 -0
- package/src/components/locations/types/index.ts +120 -0
- package/src/components/locations/utils/index.ts +5 -0
- package/src/components/logs-table/operation-log-resource.ts +41 -0
- package/src/components/logs-table/operation-log-table.component.tsx +120 -0
- package/src/components/logs-table/operation-log.scss +10 -0
- package/src/components/logs-table/operation-log.test.tsx +47 -0
- package/src/components/modal/hwr-confirmation.modal.scss +21 -0
- package/src/components/modal/hwr-confirmation.modal.tsx +170 -0
- package/src/components/modal/hwr-empty.modal.component.tsx +54 -0
- package/src/components/modal/hwr-sync.modal.scss +30 -0
- package/src/components/modal/hwr-sync.modal.tsx +209 -0
- package/src/components/modal/hwr-sync.resource.ts +23 -0
- package/src/components/provider-banner/provider-banner.component.tsx +106 -0
- package/src/components/provider-banner/provider-banner.module.scss +51 -0
- package/src/components/provider-banner/provider-banner.resource.ts +29 -0
- package/src/components/side-menu/left-panel.scss +42 -0
- package/src/components/side-menu/left-pannel.component.tsx +22 -0
- package/src/components/users/header/header.scss +90 -0
- package/src/components/users/header/user-management-header.component.tsx +42 -0
- package/src/components/users/manage-users/hooks/useProviderAttributeMapping.ts +110 -0
- package/src/components/users/manage-users/hooks/useUserFormSteps.ts +119 -0
- package/src/components/users/manage-users/hooks/useUserFormSubmission.ts +264 -0
- package/src/components/users/manage-users/hooks/useUserManagementForm.ts +122 -0
- package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-list/user-role-scope-list.component.tsx +177 -0
- package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-fields.scss +117 -0
- package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope-fields.component.tsx +290 -0
- package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +316 -0
- package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/userRoleScopeFormSchema.tsx +43 -0
- package/src/components/users/manage-users/manage-user.component.tsx +19 -0
- package/src/components/users/manage-users/manage-user.scss +31 -0
- package/src/components/users/manage-users/provider-autosuggest.component.tsx +117 -0
- package/src/components/users/manage-users/provider-search.resource.ts +34 -0
- package/src/components/users/manage-users/sections/demographic-section.component.tsx +156 -0
- package/src/components/users/manage-users/sections/login-section.component.tsx +88 -0
- package/src/components/users/manage-users/sections/provider-section.component.tsx +270 -0
- package/src/components/users/manage-users/sections/roles-section.component.tsx +88 -0
- package/src/components/users/manage-users/user-details/user-detail.scss +75 -0
- package/src/components/users/manage-users/user-details/user-details.component.tsx +182 -0
- package/src/components/users/manage-users/user-list/user-list.component.tsx +378 -0
- package/src/components/users/manage-users/user-list/user-list.resource.ts +30 -0
- package/src/components/users/manage-users/user-list/user-list.scss +37 -0
- package/src/components/users/manage-users/user-management.constants.ts +20 -0
- package/src/components/users/manage-users/user-management.utils.ts +100 -0
- package/src/components/users/manage-users/user-management.workspace.scss +172 -0
- package/src/components/users/manage-users/user-management.workspace.tsx +334 -0
- package/src/components/users/userManagementFormSchema.tsx +179 -0
- package/src/config-schema.ts +142 -0
- package/src/constants.ts +50 -0
- package/src/declarations.d.ts +2 -0
- package/src/index.ts +55 -0
- package/src/left-pannel-link.component.tsx +40 -0
- package/src/root.component.tsx +39 -0
- package/src/root.scss +12 -0
- package/src/routes.json +100 -0
- package/src/setup-tests.ts +1 -0
- package/src/types/index.ts +385 -0
- package/src/user-management.resources.ts +232 -0
- package/src/utils/utils.ts +20 -0
- package/translations/am.json +159 -0
- package/translations/en.json +159 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const PHONE_REGEX = /^\+?[\d\s\-()]+$/;
|
|
5
|
+
const MIN_PHONE_DIGITS = 9;
|
|
6
|
+
const MIN_USERNAME_LENGTH = 3;
|
|
7
|
+
const MIN_PASSWORD_LENGTH = 8;
|
|
8
|
+
|
|
9
|
+
const UserManagementFormSchema = (
|
|
10
|
+
existingUsernames: Array<string>,
|
|
11
|
+
isEdit?: boolean,
|
|
12
|
+
shouldValidatePhoneEmail = true,
|
|
13
|
+
isInitialValuesEmpty = true,
|
|
14
|
+
) => {
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
|
|
17
|
+
const userManagementFormSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
givenName: z.string().nonempty(t('givenNameRequired', 'Given name is required')),
|
|
20
|
+
middleName: z.string().optional(),
|
|
21
|
+
familyName: z.string().nonempty(t('familyNameRequired', 'Family name is required')),
|
|
22
|
+
gender: z.enum(['M', 'F'], {
|
|
23
|
+
errorMap: () => ({
|
|
24
|
+
message: t('genderRequired', 'Gender is required'),
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
phoneNumber: shouldValidatePhoneEmail
|
|
28
|
+
? z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.refine(
|
|
32
|
+
(val) => {
|
|
33
|
+
if (!val || val.trim() === '') {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const digitsOnly = val.replace(/\D/g, '');
|
|
37
|
+
return PHONE_REGEX.test(val) && digitsOnly.length >= MIN_PHONE_DIGITS;
|
|
38
|
+
},
|
|
39
|
+
{ message: t('invalidPhoneNumber', 'Invalid phone number') },
|
|
40
|
+
)
|
|
41
|
+
: z.string().optional(),
|
|
42
|
+
email: shouldValidatePhoneEmail
|
|
43
|
+
? z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.refine((val) => !val || val.trim() === '' || z.string().email().safeParse(val).success, {
|
|
47
|
+
message: t('invalidEmail', 'Invalid email address'),
|
|
48
|
+
})
|
|
49
|
+
: z.string().optional(),
|
|
50
|
+
providerIdentifiers: z.boolean().optional(),
|
|
51
|
+
username: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.transform((s) => s?.trim() ?? '')
|
|
55
|
+
.pipe(
|
|
56
|
+
z
|
|
57
|
+
.string()
|
|
58
|
+
.min(1, t('usernameRequired', 'Username is required'))
|
|
59
|
+
.min(
|
|
60
|
+
MIN_USERNAME_LENGTH,
|
|
61
|
+
t('usernameMinLength', 'Username must be at least {{count}} characters', {
|
|
62
|
+
count: MIN_USERNAME_LENGTH,
|
|
63
|
+
}),
|
|
64
|
+
)
|
|
65
|
+
.refine((value) => !existingUsernames.includes(value), {
|
|
66
|
+
message: t('usernameTaken', 'Username already exists'),
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
password: isInitialValuesEmpty
|
|
70
|
+
? z
|
|
71
|
+
.string()
|
|
72
|
+
.optional()
|
|
73
|
+
.transform((s) => s?.trim() ?? '')
|
|
74
|
+
.pipe(
|
|
75
|
+
z
|
|
76
|
+
.string()
|
|
77
|
+
.min(1, t('passwordRequired', 'Password is required'))
|
|
78
|
+
.min(
|
|
79
|
+
MIN_PASSWORD_LENGTH,
|
|
80
|
+
t('passwordMinLength', 'Password must be at least {{count}} characters', {
|
|
81
|
+
count: MIN_PASSWORD_LENGTH,
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
.refine((val) => /[a-z]/.test(val) && /[A-Z]/.test(val) && /[0-9]/.test(val), {
|
|
85
|
+
message: t(
|
|
86
|
+
'passwordCaseRequired',
|
|
87
|
+
'Password must contain at least one lowercase letter, one uppercase letter, and one number.',
|
|
88
|
+
),
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
: z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.transform((s) => s?.trim() ?? ''),
|
|
95
|
+
confirmPassword: isInitialValuesEmpty
|
|
96
|
+
? z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.transform((s) => s?.trim() ?? '')
|
|
100
|
+
.pipe(z.string().min(1, t('confirmPasswordRequired', 'Please confirm your password')))
|
|
101
|
+
: z
|
|
102
|
+
.string()
|
|
103
|
+
.optional()
|
|
104
|
+
.transform((s) => s?.trim() ?? ''),
|
|
105
|
+
roles: z
|
|
106
|
+
.array(
|
|
107
|
+
z.object({
|
|
108
|
+
uuid: z.string().min(1, 'UUID is required'),
|
|
109
|
+
display: z.string().min(1, 'Role name is required'),
|
|
110
|
+
description: z.string().nullable().optional(),
|
|
111
|
+
}),
|
|
112
|
+
)
|
|
113
|
+
.optional(),
|
|
114
|
+
primaryRole: z.string().optional(),
|
|
115
|
+
systemId: z.string().optional(),
|
|
116
|
+
providerLicense: z.string().optional(),
|
|
117
|
+
licenseExpiryDate: z.date().optional(),
|
|
118
|
+
registrationNumber: z.string().optional(),
|
|
119
|
+
qualification: z.string().optional(),
|
|
120
|
+
nationalId: z.string().optional(),
|
|
121
|
+
passportNumber: z.string().optional(),
|
|
122
|
+
isEditProvider: z.boolean().optional(),
|
|
123
|
+
providerUniqueIdentifier: z.string().optional(),
|
|
124
|
+
})
|
|
125
|
+
.refine((data) => !isInitialValuesEmpty || data.password === data.confirmPassword, {
|
|
126
|
+
message: t('passwordsDoNotMatch', 'Passwords do not match'),
|
|
127
|
+
path: ['confirmPassword'],
|
|
128
|
+
})
|
|
129
|
+
.superRefine((data, ctx) => {
|
|
130
|
+
if (isInitialValuesEmpty) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const p = (data.password ?? '').trim();
|
|
134
|
+
const c = (data.confirmPassword ?? '').trim();
|
|
135
|
+
if (!p && !c) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!p) {
|
|
139
|
+
ctx.addIssue({ code: 'custom', message: t('passwordRequired', 'Password is required'), path: ['password'] });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (p.length < MIN_PASSWORD_LENGTH) {
|
|
143
|
+
ctx.addIssue({
|
|
144
|
+
code: 'custom',
|
|
145
|
+
message: t('passwordMinLength', 'Password must be at least {{count}} characters', {
|
|
146
|
+
count: MIN_PASSWORD_LENGTH,
|
|
147
|
+
}),
|
|
148
|
+
path: ['password'],
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (!/[a-z]/.test(p) || !/[A-Z]/.test(p) || !/[0-9]/.test(p)) {
|
|
152
|
+
ctx.addIssue({
|
|
153
|
+
code: 'custom',
|
|
154
|
+
message: t(
|
|
155
|
+
'passwordCaseRequired',
|
|
156
|
+
'Password must contain at least one lowercase letter, one uppercase letter, and one number.',
|
|
157
|
+
),
|
|
158
|
+
path: ['password'],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (!c) {
|
|
162
|
+
ctx.addIssue({
|
|
163
|
+
code: 'custom',
|
|
164
|
+
message: t('confirmPasswordRequired', 'Please confirm your password'),
|
|
165
|
+
path: ['confirmPassword'],
|
|
166
|
+
});
|
|
167
|
+
} else if (p !== c) {
|
|
168
|
+
ctx.addIssue({
|
|
169
|
+
code: 'custom',
|
|
170
|
+
message: t('passwordsDoNotMatch', 'Passwords do not match'),
|
|
171
|
+
path: ['confirmPassword'],
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return { userManagementFormSchema };
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export default UserManagementFormSchema;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { fhirBaseUrl, Type } from '@openmrs/esm-framework';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
|
|
4
|
+
export const configSchema = {
|
|
5
|
+
providerNationalIdUuid: {
|
|
6
|
+
_type: Type.String,
|
|
7
|
+
_description: 'UUID for provider national id',
|
|
8
|
+
_default: '3d152c97-2293-4a2b-802e-e0f1009b7b15',
|
|
9
|
+
},
|
|
10
|
+
passportNumberUuid: {
|
|
11
|
+
_type: Type.String,
|
|
12
|
+
_description: 'UUID for passport number identification for provider',
|
|
13
|
+
_default: '5b4b88e8-9db3-41e6-a175-5e39f2c8a9a5',
|
|
14
|
+
},
|
|
15
|
+
providerHieFhirReference: {
|
|
16
|
+
_type: Type.String,
|
|
17
|
+
_description: 'UUID for provider hie fhir reference',
|
|
18
|
+
_default: '67b94e8e-4d61-4810-b0f1-d86497f6e553',
|
|
19
|
+
},
|
|
20
|
+
qualificationUuid: {
|
|
21
|
+
_type: Type.String,
|
|
22
|
+
_description: 'UUID for provider hie qualification',
|
|
23
|
+
_default: '43f99413-6e7f-4812-bc60-066bb1d43f94',
|
|
24
|
+
},
|
|
25
|
+
licenseBodyUuid: {
|
|
26
|
+
_type: Type.String,
|
|
27
|
+
_description: 'UUID for license body',
|
|
28
|
+
_default: 'ba18bb97-d17c-4640-80d2-58e7df90ca4c',
|
|
29
|
+
},
|
|
30
|
+
licenseNumberUuid: {
|
|
31
|
+
_type: Type.String,
|
|
32
|
+
_description: 'UUID for license number',
|
|
33
|
+
_default: 'bcaaa67b-cc72-4662-90c2-e1e992ceda66',
|
|
34
|
+
},
|
|
35
|
+
licenseExpiryDateUuid: {
|
|
36
|
+
_type: Type.String,
|
|
37
|
+
_description: 'UUID for license expiry date',
|
|
38
|
+
_default: '00539959-a1c7-4848-a5ed-8941e9d5e835',
|
|
39
|
+
},
|
|
40
|
+
phoneNumberUuid: {
|
|
41
|
+
_type: Type.String,
|
|
42
|
+
_description: 'UUID for provider phone number',
|
|
43
|
+
_default: '37daed7f-1f4e-4e62-8e83-6048ade18a87',
|
|
44
|
+
},
|
|
45
|
+
providerAddressUuid: {
|
|
46
|
+
_type: Type.String,
|
|
47
|
+
_description: 'UUID for provider address',
|
|
48
|
+
_default: '033ff604-ecf7-464f-b623-5b77c733667f',
|
|
49
|
+
},
|
|
50
|
+
personEmailAttributeUuid: {
|
|
51
|
+
_type: Type.String,
|
|
52
|
+
_description: 'UUID for person email attribute',
|
|
53
|
+
_default: 'b8d0b331-1d2d-4a9a-b741-1816f498bdb6',
|
|
54
|
+
},
|
|
55
|
+
personPhonenumberAttributeUuid: {
|
|
56
|
+
_type: Type.String,
|
|
57
|
+
_description: 'UUID for person phone number attribute',
|
|
58
|
+
_default: 'b2c38640-2603-4629-aebd-3b54f33f1e3a',
|
|
59
|
+
},
|
|
60
|
+
providerUniqueIdentifierAttributeTypeUuid: {
|
|
61
|
+
_type: Type.String,
|
|
62
|
+
_description: 'UUID for provider unique identifier attribute type',
|
|
63
|
+
_default: 'dace9d99-9f29-4653-9eae-c05929f34a32',
|
|
64
|
+
},
|
|
65
|
+
providerExternalIdAttributeTypeUuid: {
|
|
66
|
+
_type: Type.String,
|
|
67
|
+
_description: 'UUID for provider external id attribute type',
|
|
68
|
+
_default: 'bbdf67e8-c020-40ff-8ad6-74ba34893882',
|
|
69
|
+
},
|
|
70
|
+
providerIHRISIdentifierAttributeTypeUuid: {
|
|
71
|
+
_type: Type.String,
|
|
72
|
+
_description: 'UUID for provider IHRIS identifier attribute type',
|
|
73
|
+
_default: '5dd3eb8a-c901-4eab-bb32-a28493781aae',
|
|
74
|
+
},
|
|
75
|
+
identifierTypes: {
|
|
76
|
+
_type: Type.Array,
|
|
77
|
+
_elements: {
|
|
78
|
+
_type: Type.Object,
|
|
79
|
+
properties: {
|
|
80
|
+
key: { _type: Type.String },
|
|
81
|
+
name: { _type: Type.String },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
_default: [
|
|
85
|
+
{ key: 'National ID', name: 'National ID' },
|
|
86
|
+
{ key: 'registration_number', name: 'Registration Number' },
|
|
87
|
+
{ key: 'Passport', name: 'Passport Number' },
|
|
88
|
+
],
|
|
89
|
+
_description: 'List of identifier types with unique keys for each.',
|
|
90
|
+
},
|
|
91
|
+
regulatorOptions: {
|
|
92
|
+
_type: Type.Array,
|
|
93
|
+
_elements: {
|
|
94
|
+
_type: Type.Object,
|
|
95
|
+
properties: {
|
|
96
|
+
key: { _type: Type.String },
|
|
97
|
+
name: { _type: Type.String },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
_default: [
|
|
101
|
+
{ key: 'Clinical Officers Council', name: 'Clinical Officers Council' },
|
|
102
|
+
{ key: 'Pharmacy and Poisons Board', name: 'Pharmacy and Poisons Board' },
|
|
103
|
+
{ key: 'Nursing Council', name: 'Nursing Council' },
|
|
104
|
+
{ key: 'KMPDC', name: 'Kenya Medical Practitioners and Dentists Council (KMPDC)' },
|
|
105
|
+
{
|
|
106
|
+
key: 'Kenya Medical Laboratory Technicians & Technologists Board',
|
|
107
|
+
name: 'Kenya Medical Laboratory Technicians & Technologists Board',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
_description: 'List of regulator options with unique keys for each.',
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export interface UserProperties {
|
|
115
|
+
loginAttempts: string;
|
|
116
|
+
lastViewedPatientIds: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ConfigObject {
|
|
120
|
+
providerNationalIdUuid: string;
|
|
121
|
+
passportNumberUuid: string;
|
|
122
|
+
providerHieFhirReference: string;
|
|
123
|
+
providerUniqueIdentifierAttributeTypeUuid: string;
|
|
124
|
+
phoneNumberUuid: string;
|
|
125
|
+
providerAddressUuid: string;
|
|
126
|
+
qualificationUuid: string;
|
|
127
|
+
licenseBodyUuid: string;
|
|
128
|
+
licenseNumberUuid: string;
|
|
129
|
+
personEmailAttributeUuid: string;
|
|
130
|
+
personPhonenumberAttributeUuid: string;
|
|
131
|
+
licenseExpiryDateUuid: string;
|
|
132
|
+
providerExternalIdAttributeTypeUuid: string;
|
|
133
|
+
providerIHRISIdentifierAttributeTypeUuid: string;
|
|
134
|
+
identifierTypes: Array<{
|
|
135
|
+
key: string;
|
|
136
|
+
name: string;
|
|
137
|
+
}>;
|
|
138
|
+
regulatorOptions: Array<{
|
|
139
|
+
key: string;
|
|
140
|
+
name: string;
|
|
141
|
+
}>;
|
|
142
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const moduleName = '@palladium-ethiopia/esm-admin-app';
|
|
2
|
+
export const etlBasePath = `${window.spaBase}`;
|
|
3
|
+
|
|
4
|
+
export const today = () => {
|
|
5
|
+
const date = new Date();
|
|
6
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const DATE_PICKER_CONTROL_FORMAT = 'd/m/Y';
|
|
10
|
+
|
|
11
|
+
export const DATE_PICKER_FORMAT = 'DD/MM/YYYY';
|
|
12
|
+
|
|
13
|
+
export const formatNewDate = (date: Date | null | undefined) => {
|
|
14
|
+
return date ? new Date(date) : '';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Represents the error response from the Health Care Worker Registry API
|
|
19
|
+
* when no valid credentials are provided or found.
|
|
20
|
+
* @constant {string}
|
|
21
|
+
*/
|
|
22
|
+
export const HWR_API_NO_CREDENTIALS = 'NO_API_CREDENNTIALS';
|
|
23
|
+
/**
|
|
24
|
+
* Represents the error response from the Health Care Worker Registry API
|
|
25
|
+
* when when resource is not found
|
|
26
|
+
* @constant {string}
|
|
27
|
+
*/
|
|
28
|
+
export const RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND';
|
|
29
|
+
/**
|
|
30
|
+
* Represents the error response from the Health Care Worker Registry API
|
|
31
|
+
* When Uknown error occures
|
|
32
|
+
* @constant {string}
|
|
33
|
+
*/
|
|
34
|
+
export const UNKNOWN = 'UNKNOWN';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Represents the error response from the Health Care Worker Registry API
|
|
38
|
+
* when when prover with given identifier is not found
|
|
39
|
+
* @constant {string}
|
|
40
|
+
*/
|
|
41
|
+
export const PROVIDER_NOT_FOUND = 'PROVIDER_NOT_FOUND';
|
|
42
|
+
export const ROLE_CATEGORIES = {
|
|
43
|
+
CORE_INVENTORY: 'Core Inventory Roles',
|
|
44
|
+
};
|
|
45
|
+
export const SECTIONS = {
|
|
46
|
+
LOGIN: 'login',
|
|
47
|
+
ROLES: 'roles',
|
|
48
|
+
DEMOGRAPHIC: 'demographic',
|
|
49
|
+
PROVIDER: 'provider',
|
|
50
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineConfigSchema, getSyncLifecycle } from '@openmrs/esm-framework';
|
|
2
|
+
import { configSchema } from './config-schema';
|
|
3
|
+
import { moduleName } from './constants';
|
|
4
|
+
import OperationConfirmation from './components/confirm-modal/confirmation-operation-modal.component';
|
|
5
|
+
import Root from './root.component';
|
|
6
|
+
import ManageUserWorkspace from './components/users/manage-users/user-management.workspace';
|
|
7
|
+
import { createLeftPanelLink } from './left-pannel-link.component';
|
|
8
|
+
import HWRConfirmModal from './components/modal/hwr-confirmation.modal';
|
|
9
|
+
import HWREmptyModal from './components/modal/hwr-empty.modal.component';
|
|
10
|
+
import UserRoleScopeWorkspace from './components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace';
|
|
11
|
+
import HWRSyncModal from './components/modal/hwr-sync.modal';
|
|
12
|
+
import AddLocationWorkspace from './components/locations/forms/add-location/add-location.workspace';
|
|
13
|
+
import SearchLocationWorkspace from './components/locations/forms/search-location/search-location.workspace';
|
|
14
|
+
import ProviderBannerTag from './components/provider-banner/provider-banner.component';
|
|
15
|
+
|
|
16
|
+
const options = {
|
|
17
|
+
featureName: 'esm-admin-app',
|
|
18
|
+
moduleName,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
22
|
+
|
|
23
|
+
export const root = getSyncLifecycle(Root, options);
|
|
24
|
+
|
|
25
|
+
export function startupApp() {
|
|
26
|
+
defineConfigSchema(moduleName, configSchema);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const operationConfirmationModal = getSyncLifecycle(OperationConfirmation, options);
|
|
30
|
+
export const manageUserWorkspace = getSyncLifecycle(ManageUserWorkspace, options);
|
|
31
|
+
export const userRoleScopeWorkspace = getSyncLifecycle(UserRoleScopeWorkspace, options);
|
|
32
|
+
|
|
33
|
+
export const userManagementLeftPannelLink = getSyncLifecycle(
|
|
34
|
+
createLeftPanelLink({ title: 'Manage Users', name: 'user-management' }),
|
|
35
|
+
options,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export const locationsLeftPanelLink = getSyncLifecycle(
|
|
39
|
+
createLeftPanelLink({ title: 'Locations', name: 'locations' }),
|
|
40
|
+
options,
|
|
41
|
+
);
|
|
42
|
+
export const facilitySetupLeftPanelLink = getSyncLifecycle(
|
|
43
|
+
createLeftPanelLink({ title: 'Facility Details', name: 'facility-setup' }),
|
|
44
|
+
options,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export const hwrConfirmationModal = getSyncLifecycle(HWRConfirmModal, options);
|
|
48
|
+
export const hwrEmptyModal = getSyncLifecycle(HWREmptyModal, options);
|
|
49
|
+
export const hwrSyncModal = getSyncLifecycle(HWRSyncModal, options);
|
|
50
|
+
|
|
51
|
+
export const addLocation = getSyncLifecycle(AddLocationWorkspace, options);
|
|
52
|
+
export const searchLocationWorkspace = getSyncLifecycle(SearchLocationWorkspace, options);
|
|
53
|
+
|
|
54
|
+
// t('providerBanner', 'Provider banner')
|
|
55
|
+
export const providerBanner = getSyncLifecycle(ProviderBannerTag, options);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import last from 'lodash-es/last';
|
|
3
|
+
import { BrowserRouter, useLocation } from 'react-router-dom';
|
|
4
|
+
import { ConfigurableLink } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
export interface LinkConfig {
|
|
7
|
+
name: string;
|
|
8
|
+
title: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function LinkExtension({ config }: { config: LinkConfig }) {
|
|
12
|
+
const { name, title } = config;
|
|
13
|
+
const location = useLocation();
|
|
14
|
+
|
|
15
|
+
let urlSegment = useMemo(() => decodeURIComponent(last(location.pathname.split('/'))), [location.pathname]);
|
|
16
|
+
|
|
17
|
+
const isUUID = (value) => {
|
|
18
|
+
const regex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
|
|
19
|
+
return regex.test(value);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (isUUID(urlSegment)) {
|
|
23
|
+
urlSegment = 'user-management';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<ConfigurableLink
|
|
28
|
+
to={`${window.getOpenmrsSpaBase()}admin${name && name !== 'admin' ? `/${name}` : ''}`}
|
|
29
|
+
className={`cds--side-nav__link ${name === urlSegment && 'active-left-nav-link'}`}>
|
|
30
|
+
{title}
|
|
31
|
+
</ConfigurableLink>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const createLeftPanelLink = (config: LinkConfig) => () =>
|
|
36
|
+
(
|
|
37
|
+
<BrowserRouter>
|
|
38
|
+
<LinkExtension config={config} />
|
|
39
|
+
</BrowserRouter>
|
|
40
|
+
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|
3
|
+
import { setLeftNav, unsetLeftNav, WorkspaceContainer } from '@openmrs/esm-framework';
|
|
4
|
+
import styles from './root.scss';
|
|
5
|
+
import LeftPanel from './components/side-menu/left-pannel.component';
|
|
6
|
+
import UserManagentLandingPage from './components/users/manage-users/manage-user.component';
|
|
7
|
+
import EtlAdminDashboard from './components/dashboard/etl-dashboard.component';
|
|
8
|
+
import FacilitySetup from './components/facility-setup/facility-setup.component';
|
|
9
|
+
import HomeComponent from './components/locations/home/home-locations.component';
|
|
10
|
+
|
|
11
|
+
const Root: React.FC = () => {
|
|
12
|
+
const spaBasePath = window.spaBase;
|
|
13
|
+
const adminBasename = window.getOpenmrsSpaBase() + 'admin';
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setLeftNav({
|
|
17
|
+
name: 'admin-left-panel-slot',
|
|
18
|
+
basePath: spaBasePath,
|
|
19
|
+
});
|
|
20
|
+
return () => unsetLeftNav('admin-left-panel-slot');
|
|
21
|
+
}, [spaBasePath]);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<BrowserRouter basename={adminBasename}>
|
|
25
|
+
<LeftPanel />
|
|
26
|
+
<main className={styles.container}>
|
|
27
|
+
<Routes>
|
|
28
|
+
<Route path="/" element={<UserManagentLandingPage />} />
|
|
29
|
+
<Route path="/user-management" element={<UserManagentLandingPage />} />
|
|
30
|
+
<Route path="/etl-administration" element={<EtlAdminDashboard />} />
|
|
31
|
+
<Route path="/facility-setup" element={<FacilitySetup />} />
|
|
32
|
+
<Route path="/locations" element={<HomeComponent />} />
|
|
33
|
+
</Routes>
|
|
34
|
+
</main>
|
|
35
|
+
</BrowserRouter>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default Root;
|
package/src/root.scss
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
background-color: colors.$white-0;
|
|
7
|
+
height: calc(100vh - layout.$spacing-09);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
:global(.omrs-breakpoint-gt-tablet) .container {
|
|
11
|
+
margin-left: var(--omrs-sidenav-width);
|
|
12
|
+
}
|
package/src/routes.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.openmrs.org/routes.schema.json",
|
|
3
|
+
"backendDependencies": {
|
|
4
|
+
"kenyaemrCharts": "^1.6.7"
|
|
5
|
+
},
|
|
6
|
+
"extensions": [
|
|
7
|
+
{
|
|
8
|
+
"component": "adminLeftPanelLink",
|
|
9
|
+
"name": "admin-left-panel-link",
|
|
10
|
+
"slot": "admin-left-panel-slot"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"component": "userManagementLeftPannelLink",
|
|
14
|
+
"name": "user-management-left-panel-link",
|
|
15
|
+
"slot": "admin-left-panel-slot"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"component": "locationsLeftPanelLink",
|
|
19
|
+
"name": "locations-left-panel-link",
|
|
20
|
+
"slot": "admin-left-panel-slot"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"component": "facilitySetupLeftPanelLink",
|
|
24
|
+
"name": "facility-setup-left-panel-link",
|
|
25
|
+
"slot": "admin-left-panel-slot"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"component": "providerBanner",
|
|
29
|
+
"name": "provider-banner",
|
|
30
|
+
"slot": "provider-banner-info-slot",
|
|
31
|
+
"order":1
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"workspaces": [
|
|
35
|
+
{
|
|
36
|
+
"name": "manage-user-workspace",
|
|
37
|
+
"component": "manageUserWorkspace",
|
|
38
|
+
"title": "Manage User Workspace",
|
|
39
|
+
"type": "other-form",
|
|
40
|
+
"canMaximize": true,
|
|
41
|
+
"width": "extra-wide"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "user-role-scope-workspace",
|
|
45
|
+
"component": "userRoleScopeWorkspace",
|
|
46
|
+
"title": "User Rple Scope Workspace",
|
|
47
|
+
"type": "other-form",
|
|
48
|
+
"canMaximize": true,
|
|
49
|
+
"width": "extra-wide"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "add-location-workspace",
|
|
53
|
+
"title": "Add Location",
|
|
54
|
+
"component": "addLocation",
|
|
55
|
+
"type": "workspace"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "search-location-workspace",
|
|
59
|
+
"title": "Search Location",
|
|
60
|
+
"component": "searchLocationWorkspace",
|
|
61
|
+
"type": "workspace"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "hwr-sync-workspace",
|
|
65
|
+
"title": "HWR Sync Workspace",
|
|
66
|
+
"component": "hwrSyncWorkspace",
|
|
67
|
+
"type": "other-form"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "hwr-sync-modal",
|
|
71
|
+
"title": "HWR Sync Modal",
|
|
72
|
+
"component": "hwrSyncModal",
|
|
73
|
+
"type": "modal"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"modals": [
|
|
77
|
+
{
|
|
78
|
+
"component": "operationConfirmationModal",
|
|
79
|
+
"name": "operation-confirmation-modal"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"component": "hwrConfirmationModal",
|
|
83
|
+
"name": "hwr-confirmation-modal"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"component": "hwrEmptyModal",
|
|
87
|
+
"name": "hwr-empty-modal"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"component": "hwrSyncModal",
|
|
91
|
+
"name": "hwr-syncing-modal"
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"pages": [
|
|
95
|
+
{
|
|
96
|
+
"component": "root",
|
|
97
|
+
"route": "admin"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/extend-expect';
|