@kenyaemr/esm-patient-registration-app 6.0.1-pre.1.0.0 → 6.0.1-pre.1.0.2

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 (49) hide show
  1. package/dist/130.js +1 -1
  2. package/dist/130.js.map +1 -1
  3. package/dist/481.js +1 -0
  4. package/dist/481.js.map +1 -0
  5. package/dist/59.js +1 -1
  6. package/dist/59.js.map +1 -1
  7. package/dist/676.js +1 -0
  8. package/dist/676.js.map +1 -0
  9. package/dist/762.js +2 -0
  10. package/dist/{895.js.LICENSE.txt → 762.js.LICENSE.txt} +0 -7
  11. package/dist/762.js.map +1 -0
  12. package/dist/788.js +1 -1
  13. package/dist/816.js +1 -0
  14. package/dist/816.js.map +1 -0
  15. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  16. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +92 -92
  17. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  18. package/dist/main.js +1 -1
  19. package/dist/main.js.LICENSE.txt +0 -7
  20. package/dist/main.js.map +1 -1
  21. package/dist/routes.json +1 -1
  22. package/package.json +2 -6
  23. package/src/config-schema.ts +12 -9
  24. package/src/index.ts +2 -2
  25. package/src/patient-photo.extension.tsx +9 -0
  26. package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -0
  27. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +3 -0
  28. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +1 -0
  29. package/src/patient-registration/field/phone/phone-field.component.tsx +1 -0
  30. package/src/patient-registration/form-manager.ts +4 -2
  31. package/src/patient-registration/patient-registration.component.tsx +8 -2
  32. package/src/patient-registration/patient-registration.resource.ts +1 -47
  33. package/src/patient-registration/patient-registration.test.tsx +0 -9
  34. package/src/patient-verification/patient-verification-hook.tsx +5 -5
  35. package/src/patient-verification/patient-verification-utils.ts +4 -4
  36. package/src/patient-verification/patient-verification.component.tsx +15 -8
  37. package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +3 -3
  38. package/src/routes.json +1 -1
  39. package/dist/619.js +0 -1
  40. package/dist/619.js.map +0 -1
  41. package/dist/62.js +0 -1
  42. package/dist/62.js.map +0 -1
  43. package/dist/840.js +0 -1
  44. package/dist/840.js.map +0 -1
  45. package/dist/895.js +0 -2
  46. package/dist/895.js.map +0 -1
  47. package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +0 -60
  48. package/src/widgets/display-photo.component.tsx +0 -30
  49. package/src/widgets/display-photo.test.tsx +0 -37
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true},{"component":"cancelPatientEditModal","name":"cancel-patient-edit-modal","online":true,"offline":true},{"component":"patientPhoto","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true},{"component":"deleteIdentifierConfirmationModal","name":"delete-identifier-confirmation-modal","online":true,"offline":true},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal","online":true,"offline":true},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal","online":true,"offline":true}],"version":"6.0.1-pre.1.0.0"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true},{"component":"cancelPatientEditModal","name":"cancel-patient-edit-modal","online":true,"offline":true},{"component":"patientPhotoExtension","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true},{"component":"deleteIdentifierConfirmationModal","name":"delete-identifier-confirmation-modal","online":true,"offline":true},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal","online":true,"offline":true},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal","online":true,"offline":true}],"version":"6.0.1-pre.1.0.2"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-patient-registration-app",
3
- "version": "6.0.1-pre.1.0.0",
3
+ "version": "6.0.1-pre.1.0.2",
4
4
  "description": "Patient registration microfrontend for the OpenMRS SPA",
5
5
  "browser": "dist/kenyaemr-esm-patient-registration-app.js",
6
6
  "main": "src/index.ts",
@@ -38,11 +38,8 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@carbon/react": "~1.37.0",
41
- "core-js-pure": "^3.34.0",
42
41
  "formik": "^2.1.5",
43
- "geopattern": "^1.2.3",
44
42
  "lodash-es": "^4.17.15",
45
- "react-avatar": "^5.0.3",
46
43
  "uuid": "^8.3.2",
47
44
  "yup": "^0.29.1"
48
45
  },
@@ -56,6 +53,5 @@
56
53
  },
57
54
  "devDependencies": {
58
55
  "webpack": "^5.74.0"
59
- },
60
- "stableVersion": "6.0.0"
56
+ }
61
57
  }
@@ -60,14 +60,15 @@ export interface RegistrationConfig {
60
60
  };
61
61
  phone: {
62
62
  personAttributeUuid: string;
63
+ validation?: {
64
+ required: boolean;
65
+ matches?: string;
66
+ };
63
67
  };
64
68
  };
65
69
  links: {
66
70
  submitButton: string;
67
71
  };
68
- concepts: {
69
- patientPhotoUuid: string;
70
- };
71
72
  defaultPatientIdentifierTypes: Array<string>;
72
73
  registrationObs: {
73
74
  encounterTypeUuid: string | null;
@@ -312,6 +313,14 @@ export const esmPatientRegistrationSchema = {
312
313
  _default: '14d4f066-15f5-102d-96e4-000c29c2a5d7',
313
314
  _description: 'The UUID of the phone number person attribute type',
314
315
  },
316
+ validation: {
317
+ required: { _type: Type.Boolean, _default: false },
318
+ matches: {
319
+ _type: Type.String,
320
+ _default: null,
321
+ _description: 'Optional RegEx for testing the validity of the input.',
322
+ },
323
+ },
315
324
  },
316
325
  },
317
326
  links: {
@@ -321,12 +330,6 @@ export const esmPatientRegistrationSchema = {
321
330
  _validators: [validators.isUrlWithTemplateParameters(['patientUuid'])],
322
331
  },
323
332
  },
324
- concepts: {
325
- patientPhotoUuid: {
326
- _type: Type.ConceptUuid,
327
- _default: '736e8771-e501-4615-bfa7-570c03f4bef5',
328
- },
329
- },
330
333
  defaultPatientIdentifierTypes: {
331
334
  _type: Type.Array,
332
335
  _elements: {
package/src/index.ts CHANGED
@@ -4,8 +4,8 @@ import { moduleName, patientRegistration } from './constants';
4
4
  import { setupOffline } from './offline';
5
5
  import rootComponent from './root.component';
6
6
  import addPatientLinkComponent from './add-patient-link';
7
- import patientPhotoComponent from './widgets/display-photo.component';
8
7
  import editPatientDetailsButtonComponent from './widgets/edit-patient-details-button.component';
8
+ import { PatientPhotoExtension } from './patient-photo.extension';
9
9
 
10
10
  export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
11
11
 
@@ -55,7 +55,7 @@ export const cancelPatientEditModal = getAsyncLifecycle(
55
55
  options,
56
56
  );
57
57
 
58
- export const patientPhoto = getSyncLifecycle(patientPhotoComponent, options);
58
+ export const patientPhotoExtension = getSyncLifecycle(PatientPhotoExtension, options);
59
59
 
60
60
  export const editPatientDetailsButton = getSyncLifecycle(editPatientDetailsButtonComponent, {
61
61
  featureName: 'edit-patient-details',
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { PatientPhoto, type PatientPhotoProps } from '@openmrs/esm-framework';
3
+
4
+ export function PatientPhotoExtension(props: PatientPhotoProps) {
5
+ console.warn(
6
+ 'Using the patient-photo extension (or patient-photo-slot slot) is deprecated. Please use the PatientPhoto component from @openmrs/esm-framework.',
7
+ );
8
+ return <PatientPhoto {...props} />;
9
+ }
@@ -21,6 +21,7 @@ export const AddressField: React.FC<AddressFieldProps> = ({ fieldDefinition }) =
21
21
  <Input
22
22
  id={fieldDefinition.id}
23
23
  labelText={t(`${fieldDefinition.label}`, `${fieldDefinition.label}`)}
24
+ required={fieldDefinition?.validation?.required ?? false}
24
25
  {...field}
25
26
  />
26
27
  );
@@ -14,6 +14,7 @@ export interface CodedPersonAttributeFieldProps {
14
14
  answerConceptSetUuid: string;
15
15
  label?: string;
16
16
  customConceptAnswers: Array<{ uuid: string; label?: string }>;
17
+ required: boolean;
17
18
  }
18
19
 
19
20
  export function CodedPersonAttributeField({
@@ -22,6 +23,7 @@ export function CodedPersonAttributeField({
22
23
  answerConceptSetUuid,
23
24
  label,
24
25
  customConceptAnswers,
26
+ required,
25
27
  }: CodedPersonAttributeFieldProps) {
26
28
  const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(
27
29
  customConceptAnswers.length ? '' : answerConceptSetUuid,
@@ -100,6 +102,7 @@ export function CodedPersonAttributeField({
100
102
  name={`person-attribute-${personAttributeType.uuid}`}
101
103
  labelText={label ?? personAttributeType?.display}
102
104
  invalid={errors[fieldName] && touched[fieldName]}
105
+ required={required}
103
106
  {...field}>
104
107
  <SelectItem value={''} text={t('selectAnOption', 'Select an option')} />
105
108
  {answers.map((answer) => (
@@ -38,6 +38,7 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
38
38
  label={fieldDefinition.label}
39
39
  id={fieldDefinition?.id}
40
40
  customConceptAnswers={fieldDefinition.customConceptAnswers ?? []}
41
+ required={fieldDefinition.validation?.required ?? false}
41
42
  />
42
43
  );
43
44
  default:
@@ -10,6 +10,7 @@ export function PhoneField() {
10
10
  id: 'phone',
11
11
  type: 'person attribute',
12
12
  uuid: config.fieldConfigurations.phone.personAttributeUuid,
13
+ validation: config.fieldConfigurations.phone.validation,
13
14
  showHeading: false,
14
15
  };
15
16
  return <PersonAttributeField fieldDefinition={fieldDefinition} />;
@@ -4,6 +4,7 @@ import {
4
4
  queueSynchronizationItem,
5
5
  type Session,
6
6
  restBaseUrl,
7
+ getConfig,
7
8
  } from '@openmrs/esm-framework';
8
9
  import { patientRegistration } from '../constants';
9
10
  import {
@@ -130,13 +131,14 @@ export class FormManager {
130
131
 
131
132
  await this.saveObservations(values.obs, savePatientResponse, currentLocation, currentUser, config);
132
133
 
133
- if (config.concepts.patientPhotoUuid && capturePhotoProps?.imageData) {
134
+ const { patientPhotoUuid } = await getConfig('@openmrs/esm-styleguide');
135
+ if (patientPhotoUuid && capturePhotoProps?.imageData) {
134
136
  await savePatientPhoto(
135
137
  savePatientResponse.data.uuid,
136
138
  capturePhotoProps.imageData,
137
139
  `${restBaseUrl}/obs`,
138
140
  capturePhotoProps.dateTime || new Date().toISOString(),
139
- config.concepts.patientPhotoUuid,
141
+ patientPhotoUuid,
140
142
  );
141
143
  }
142
144
  }
@@ -5,12 +5,18 @@ import { XAxis, ShareKnowledge } from '@carbon/react/icons';
5
5
  import { useLocation, useParams } from 'react-router-dom';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Formik, Form, type FormikHelpers } from 'formik';
8
- import { createErrorHandler, showSnackbar, useConfig, interpolateUrl, usePatient } from '@openmrs/esm-framework';
8
+ import {
9
+ createErrorHandler,
10
+ showSnackbar,
11
+ useConfig,
12
+ interpolateUrl,
13
+ usePatient,
14
+ usePatientPhoto,
15
+ } from '@openmrs/esm-framework';
9
16
  import { getValidationSchema } from './validation/patient-registration-validation';
10
17
  import { type FormValues, type CapturePhotoProps } from './patient-registration.types';
11
18
  import { PatientRegistrationContext } from './patient-registration-context';
12
19
  import { type SavePatientForm, SavePatientTransactionManager } from './form-manager';
13
- import { usePatientPhoto } from './patient-registration.resource';
14
20
  import { DummyDataInput } from './input/dummy-data/dummy-data-input.component';
15
21
  import {
16
22
  cancelRegistration,
@@ -1,5 +1,4 @@
1
- import useSWR from 'swr';
2
- import { openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
3
2
  import { type Patient, type Relationship, type PatientIdentifier, type Encounter } from './patient-registration.types';
4
3
 
5
4
  export const uuidIdentifier = '05a29f94-c0ed-11e2-94be-8c13b969e334';
@@ -134,51 +133,6 @@ export async function savePatientPhoto(
134
133
  });
135
134
  }
136
135
 
137
- interface ObsFetchResponse {
138
- results: Array<PhotoObs>;
139
- }
140
-
141
- interface PhotoObs {
142
- display: string;
143
- obsDatetime: string;
144
- uuid: string;
145
- value: {
146
- display: string;
147
- links: {
148
- rel: string;
149
- uri: string;
150
- };
151
- };
152
- }
153
-
154
- interface UsePatientPhotoResult {
155
- data: { dateTime: string; imageSrc: string } | null;
156
- isError: Error;
157
- isLoading: boolean;
158
- }
159
-
160
- export function usePatientPhoto(patientUuid: string): UsePatientPhotoResult {
161
- const {
162
- concepts: { patientPhotoUuid },
163
- } = useConfig();
164
- const url = `${restBaseUrl}/obs?patient=${patientUuid}&concept=${patientPhotoUuid}&v=full`;
165
-
166
- const { data, error, isLoading } = useSWR<{ data: ObsFetchResponse }, Error>(patientUuid ? url : null, openmrsFetch);
167
-
168
- const item = data?.data?.results[0];
169
-
170
- return {
171
- data: item
172
- ? {
173
- dateTime: item?.obsDatetime,
174
- imageSrc: item?.value?.links?.uri,
175
- }
176
- : null,
177
- isError: error,
178
- isLoading,
179
- };
180
- }
181
-
182
136
  export async function fetchPerson(query: string, abortController: AbortController) {
183
137
  const [patientsRes, personsRes] = await Promise.all([
184
138
  openmrsFetch(`${restBaseUrl}/patient?q=${query}`, {
@@ -75,15 +75,6 @@ jest.mock('./field/field.resource', () => ({
75
75
  }),
76
76
  }));
77
77
 
78
- jest.mock('@openmrs/esm-framework', () => {
79
- const originalModule = jest.requireActual('@openmrs/esm-framework');
80
-
81
- return {
82
- ...originalModule,
83
- validator: jest.fn(),
84
- };
85
- });
86
-
87
78
  jest.mock('react-router-dom', () => ({
88
79
  ...(jest.requireActual('react-router-dom') as any),
89
80
  useLocation: () => ({
@@ -1,4 +1,4 @@
1
- import { type FetchResponse, openmrsFetch, showNotification, showToast } from '@openmrs/esm-framework';
1
+ import { type FetchResponse, openmrsFetch, showNotification, showToast, showSnackbar } from '@openmrs/esm-framework';
2
2
  import { generateNUPIPayload, handleClientRegistryResponse } from './patient-verification-utils';
3
3
  import useSWR from 'swr';
4
4
  import useSWRImmutable from 'swr/immutable';
@@ -47,12 +47,12 @@ export async function handleSavePatientToClientRegistry(
47
47
  postToRegistry(formValues, setValues);
48
48
  }
49
49
  } catch (error) {
50
- showToast({
50
+ showSnackbar({
51
51
  title: 'Client registry error',
52
- description: `${error}`,
53
- millis: 10000,
52
+ subtitle: `${error}`,
53
+ timeoutInMs: 10000,
54
54
  kind: 'error',
55
- critical: true,
55
+ isLowContrast: true,
56
56
  });
57
57
  }
58
58
  return;
@@ -62,7 +62,7 @@ export function handleClientRegistryResponse(
62
62
  required: false,
63
63
  identifierTypeUuid: '49af6cdc-7968-4abb-bf46-de10d7f4859f',
64
64
  identifierName: 'National ID',
65
- identifierValue: identifications !== undefined && identifications[0]?.identificationNumber,
65
+ identifierValue: identifications !== undefined && identifications[0]?.identificationNumber?.trim(),
66
66
  },
67
67
 
68
68
  ['nationalUniquePatientIdentifier']: {
@@ -81,9 +81,9 @@ export function handleClientRegistryResponse(
81
81
  onConfirm: () => {
82
82
  props.setValues({
83
83
  ...props.values,
84
- familyName: lastName,
85
- middleName: middleName,
86
- givenName: firstName,
84
+ familyName: lastName?.trim() ?? '',
85
+ middleName: middleName?.trim() ?? '',
86
+ givenName: firstName.trim() ?? '',
87
87
  gender: clientResponse.client.gender,
88
88
  birthdate: new Date(dateOfBirth),
89
89
  isDead: !isAlive,
@@ -1,10 +1,10 @@
1
1
  import React, { useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { Tile, ComboBox, Layer, Button, Search, InlineLoading } from '@carbon/react';
3
+ import { Tile, ComboBox, Layer, Button, Search, InlineLoading, InlineNotification } from '@carbon/react';
4
4
  import styles from './patient-verification.scss';
5
5
  import { countries, verificationIdentifierTypes } from './assets/verification-assets';
6
6
  import { searchClientRegistry, useGlobalProperties } from './patient-verification-hook';
7
- import { showToast } from '@openmrs/esm-framework';
7
+ import { showSnackbar } from '@openmrs/esm-framework';
8
8
  import { handleClientRegistryResponse } from './patient-verification-utils';
9
9
  import { type FormikProps } from 'formik';
10
10
  import { type FormValues } from '../patient-registration/patient-registration.types';
@@ -37,21 +37,28 @@ const PatientVerification: React.FC<PatientVerificationProps> = ({ props }) => {
37
37
 
38
38
  handleClientRegistryResponse(clientRegistryResponse, props, verificationCriteria.searchTerm);
39
39
  } catch (error) {
40
- showToast({
40
+ showSnackbar({
41
41
  title: 'Client registry error',
42
- description: `Please reload the registration page and re-try again, if the issue persist contact system administrator`,
43
- millis: 10000,
42
+ subtitle: `Please reload the registration page and re-try again, if the issue persist contact system administrator`,
43
+ timeoutInMs: 10000,
44
44
  kind: 'error',
45
- critical: true,
45
+ isLowContrast: true,
46
46
  });
47
47
  setIsLoadingSearch(false);
48
48
  }
49
49
  };
50
50
 
51
- if (error) {
51
+ if (!error) {
52
52
  return (
53
53
  <Tile className={styles.errorWrapper}>
54
- <p>Error occurred while reaching the client registry, please proceed with registration and try again later</p>
54
+ <InlineNotification
55
+ aria-label="closes notification"
56
+ kind="error"
57
+ statusIconDescription="notification"
58
+ subtitle="Access to national client registry may be blocked by a proxy server. Please proceed with registration and try again later. Contact your system administrator if the issue persists."
59
+ title="Error: Failed to reach client registry."
60
+ lowContrast={true}
61
+ />
55
62
  </Tile>
56
63
  );
57
64
  }
@@ -39,7 +39,7 @@ const ConfirmPrompt: React.FC<ConfirmPromptProps> = ({ close, onConfirm, patient
39
39
  <div style={{ display: 'flex', margin: '1rem' }}>
40
40
  <ExtensionSlot
41
41
  style={{ display: 'flex', alignItems: 'center' }}
42
- name="patient-photo-slot"
42
+ extensionSlotName="patient-photo-slot"
43
43
  state={{ patientName: `${patient?.firstName} ${patient?.lastName}` }}
44
44
  />
45
45
  <div style={{ width: '100%', marginLeft: '0.625rem' }}>
@@ -54,8 +54,8 @@ const ConfirmPrompt: React.FC<ConfirmPromptProps> = ({ close, onConfirm, patient
54
54
  <PatientInfo label={t('age', 'Age')} value={age(patient?.dateOfBirth)} />
55
55
  <PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.dateOfBirth))} />
56
56
  <PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
57
- <PatientInfo label={t('nupiIdentification', 'NUPI')} value={patient?.clientNumber} />
58
- <PatientInfo label={t('shaNumber', 'SHA Number')} value={'--'} />
57
+ <PatientInfo label={t('NUPI')} value={patient?.clientNumber} />
58
+ <PatientInfo label={t('SHA Number')} value={'--'} />
59
59
  </div>
60
60
  </div>
61
61
  </div>
package/src/routes.json CHANGED
@@ -32,7 +32,7 @@
32
32
  "offline": true
33
33
  },
34
34
  {
35
- "component": "patientPhoto",
35
+ "component": "patientPhotoExtension",
36
36
  "name": "patient-photo-widget",
37
37
  "slot": "patient-photo-slot",
38
38
  "online": true,