@kenyaemr/esm-patient-registration-app 8.0.1-pre.99 → 8.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 (133) hide show
  1. package/.turbo/turbo-build.log +23 -22
  2. package/dist/108.js +1 -0
  3. package/dist/108.js.map +1 -0
  4. package/dist/130.js +1 -1
  5. package/dist/130.js.LICENSE.txt +2 -0
  6. package/dist/130.js.map +1 -1
  7. package/dist/2.js +1 -0
  8. package/dist/2.js.map +1 -0
  9. package/dist/250.js +1 -0
  10. package/dist/250.js.map +1 -0
  11. package/dist/271.js +1 -1
  12. package/dist/319.js +1 -1
  13. package/dist/325.js +1 -0
  14. package/dist/325.js.map +1 -0
  15. package/dist/372.js +2 -0
  16. package/dist/372.js.map +1 -0
  17. package/dist/460.js +1 -1
  18. package/dist/574.js +1 -1
  19. package/dist/644.js +1 -1
  20. package/dist/66.js +1 -0
  21. package/dist/66.js.map +1 -0
  22. package/dist/662.js +1 -0
  23. package/dist/662.js.map +1 -0
  24. package/dist/757.js +1 -1
  25. package/dist/{59.js → 76.js} +1 -1
  26. package/dist/{59.js.map → 76.js.map} +1 -1
  27. package/dist/788.js +1 -1
  28. package/dist/807.js +1 -1
  29. package/dist/833.js +1 -1
  30. package/dist/895.js +2 -0
  31. package/dist/895.js.LICENSE.txt +34 -0
  32. package/dist/895.js.map +1 -0
  33. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  34. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +161 -188
  35. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  36. package/dist/main.js +1 -1
  37. package/dist/main.js.LICENSE.txt +10 -0
  38. package/dist/main.js.map +1 -1
  39. package/dist/routes.json +1 -1
  40. package/package-lock.json +6047 -0
  41. package/package.json +3 -4
  42. package/src/client-registry/client-registry.component.tsx +22 -0
  43. package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +134 -0
  44. package/src/client-registry/hie-client-registry/hie-client-registry.scss +53 -0
  45. package/src/client-registry/hie-client-registry/hie-resource.ts +162 -0
  46. package/src/client-registry/hie-client-registry/hie-types.ts +29 -0
  47. package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +82 -0
  48. package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +10 -0
  49. package/src/{patient-verification → client-registry/patient-verification}/patient-verification-hook.tsx +1 -1
  50. package/src/{patient-verification → client-registry/patient-verification}/patient-verification-utils.ts +1 -1
  51. package/src/{patient-verification → client-registry/patient-verification}/patient-verification.component.tsx +1 -1
  52. package/src/{patient-verification → client-registry/patient-verification}/patient-verification.scss +1 -1
  53. package/src/{patient-verification → client-registry/patient-verification}/verification-modal/empty-prompt.component.tsx +9 -6
  54. package/src/config-schema.ts +72 -2
  55. package/src/index.ts +6 -6
  56. package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +98 -0
  57. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +84 -0
  58. package/src/patient-registration/field/dob/dob.component.tsx +21 -7
  59. package/src/patient-registration/field/field.component.tsx +11 -5
  60. package/src/patient-registration/field/field.resource.ts +11 -4
  61. package/src/patient-registration/field/field.scss +44 -5
  62. package/src/patient-registration/field/gender/gender-field.component.tsx +2 -1
  63. package/src/patient-registration/field/gender/gender-field.test.tsx +1 -0
  64. package/src/patient-registration/field/id/id-field.component.tsx +8 -6
  65. package/src/patient-registration/field/id/id-field.test.tsx +27 -8
  66. package/src/patient-registration/field/name/name-field.component.tsx +5 -1
  67. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +1 -0
  68. package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +76 -27
  69. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +105 -0
  70. package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +48 -0
  71. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +12 -1
  72. package/src/patient-registration/field/person-attributes/useUpdateIdentifierRequirement.tsx +83 -0
  73. package/src/patient-registration/form-manager.test.ts +21 -0
  74. package/src/patient-registration/form-manager.ts +40 -20
  75. package/src/patient-registration/input/basic-input/input/input.component.tsx +5 -1
  76. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +18 -10
  77. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +166 -67
  78. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +3 -0
  79. package/src/patient-registration/input/input.scss +5 -0
  80. package/src/patient-registration/patient-registration-context.ts +4 -3
  81. package/src/patient-registration/patient-registration-hooks.ts +67 -9
  82. package/src/patient-registration/patient-registration-utils.ts +3 -7
  83. package/src/patient-registration/patient-registration.component.tsx +44 -30
  84. package/src/patient-registration/patient-registration.resource.ts +8 -0
  85. package/src/patient-registration/patient-registration.test.tsx +9 -3
  86. package/src/patient-registration/patient-registration.types.ts +4 -1
  87. package/src/patient-registration/section/death-info/death-info-section.component.tsx +22 -17
  88. package/src/patient-registration/section/death-info/death-info-section.test.tsx +4 -14
  89. package/src/patient-registration/section/section.component.tsx +1 -1
  90. package/src/patient-registration/section/section.scss +5 -0
  91. package/src/patient-registration/validation/{patient-registration-validation.test.tsx → patient-registration-validation.test.ts} +26 -4
  92. package/src/patient-registration/validation/patient-registration-validation.ts +126 -0
  93. package/src/routes.json +14 -17
  94. package/src/widgets/cancel-patient-edit.modal.tsx +33 -0
  95. package/src/widgets/cancel-patient-edit.test.tsx +2 -3
  96. package/src/widgets/delete-identifier-confirmation.modal.tsx +22 -15
  97. package/src/widgets/delete-identifier-confirmation.test.tsx +2 -1
  98. package/translations/am.json +36 -25
  99. package/translations/ar.json +37 -26
  100. package/translations/en.json +51 -20
  101. package/translations/es.json +38 -26
  102. package/translations/fr.json +47 -35
  103. package/translations/he.json +37 -30
  104. package/translations/km.json +37 -30
  105. package/translations/zh.json +37 -20
  106. package/translations/zh_CN.json +37 -20
  107. package/dist/152.js +0 -1
  108. package/dist/152.js.map +0 -1
  109. package/dist/255.js +0 -2
  110. package/dist/255.js.map +0 -1
  111. package/dist/303.js +0 -1
  112. package/dist/303.js.map +0 -1
  113. package/dist/330.js +0 -1
  114. package/dist/330.js.map +0 -1
  115. package/dist/564.js +0 -1
  116. package/dist/564.js.map +0 -1
  117. package/dist/623.js +0 -1
  118. package/dist/623.js.map +0 -1
  119. package/dist/729.js +0 -1
  120. package/dist/729.js.map +0 -1
  121. package/dist/735.js +0 -1
  122. package/dist/735.js.map +0 -1
  123. package/dist/831.js +0 -2
  124. package/dist/831.js.LICENSE.txt +0 -14
  125. package/dist/831.js.map +0 -1
  126. package/src/patient-registration/validation/patient-registration-validation.tsx +0 -60
  127. package/src/widgets/cancel-patient-edit.component.tsx +0 -37
  128. package/src/widgets/delete-identifier-confirmation.scss +0 -34
  129. /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
  130. /package/src/{patient-verification → client-registry/patient-verification}/assets/counties.json +0 -0
  131. /package/src/{patient-verification → client-registry/patient-verification}/assets/verification-assets.ts +0 -0
  132. /package/src/{patient-verification → client-registry/patient-verification}/verification-modal/confirm-prompt.component.tsx +0 -0
  133. /package/src/{patient-verification → client-registry/patient-verification}/verification-types.ts +0 -0
@@ -66,7 +66,7 @@ export interface TextInputProps
66
66
  * `true` to use the light version. For use on $ui-01 backgrounds only.
67
67
  * Don't use this to make tile background color same as container background color.
68
68
  * 'The `light` prop for `TextInput` has ' +
69
- 'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'
69
+ 'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'
70
70
  */
71
71
  light?: boolean;
72
72
 
@@ -145,6 +145,10 @@ export const Input: React.FC<InputProps> = ({ checkWarning, ...props }) => {
145
145
  t('invalidEmail')
146
146
  t('numberInNameDubious')
147
147
  t('yearsEstimateRequired')
148
+ t('deathdayIsRequired', 'Death date is required when the patient is marked as deceased.')
149
+ t('deathdayInvalidDate', 'Date of death is invalid')
150
+ t('deathCauseRequired', 'Cause of death is required')
151
+ t('nonCodedCauseOfDeathRequired', 'Non-coded cause of death is required')
148
152
  */
149
153
 
150
154
  const value = field.value || '';
@@ -26,7 +26,8 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
26
26
  () => identifierTypes.find((identifierType) => identifierType.uuid === patientIdentifier.identifierTypeUuid),
27
27
  [patientIdentifier, identifierTypes],
28
28
  );
29
- const { autoGeneration, initialValue, identifierValue, identifierName, required } = patientIdentifier;
29
+ const { autoGeneration, initialValue, identifierValue, identifierName, required, selectedSource } = patientIdentifier;
30
+ const manualEntryEnabled = selectedSource?.autoGenerationOption?.manualEntryEnabled;
30
31
  const [hideInputField, setHideInputField] = useState(autoGeneration || initialValue === identifierValue);
31
32
  const name = `identifiers.${fieldName}.identifierValue`;
32
33
  const [identifierField, identifierFieldMeta] = useField(name);
@@ -46,8 +47,8 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
46
47
  setFieldValue(`identifiers.${fieldName}`, {
47
48
  ...patientIdentifier,
48
49
  identifierValue: initialValue,
49
- selectedSource: null,
50
- autoGeneration: false,
50
+ selectedSource,
51
+ autoGeneration,
51
52
  } as PatientIdentifierValue);
52
53
  // eslint-disable-next-line react-hooks/exhaustive-deps
53
54
  }, [initialValue, setHideInputField]);
@@ -57,6 +58,7 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
57
58
  setFieldValue(`identifiers.${fieldName}`, {
58
59
  ...patientIdentifier,
59
60
  ...setIdentifierSource(identifierType?.identifierSources?.[0], initialValue, initialValue),
61
+ ...(autoGeneration && manualEntryEnabled && { identifierValue: initialValue ?? '' }),
60
62
  });
61
63
  };
62
64
 
@@ -83,9 +85,12 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
83
85
  }
84
86
  };
85
87
 
88
+ const showEditButton = !required && hideInputField && (!!initialValue || manualEntryEnabled);
89
+ const showResetButton =
90
+ (!!initialValue && initialValue !== identifierValue) || (!hideInputField && manualEntryEnabled);
86
91
  return (
87
92
  <div className={styles.IDInput}>
88
- {!autoGeneration && !hideInputField ? (
93
+ {!hideInputField ? (
89
94
  <Input
90
95
  id={name}
91
96
  labelText={identifierName}
@@ -99,21 +104,24 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
99
104
  />
100
105
  ) : (
101
106
  <div className={styles.textID}>
102
- <p className={styles.label}>{identifierName}</p>
103
- <p className={styles.bodyShort02}>
107
+ <p data-testid="identifier-label" className={styles.label}>
108
+ {required ? identifierName : `${t('optionalIdentifierLabel', { identifierName })}`}
109
+ </p>
110
+ <p data-testid="identifier-placeholder" className={styles.bodyShort02}>
104
111
  {autoGeneration ? t('autoGeneratedPlaceholderText', 'Auto-generated') : identifierValue}
105
112
  </p>
106
- <input type="hidden" {...identifierField} disabled />
113
+ <input data-testid="identifier-input" type="hidden" {...identifierField} disabled />
107
114
  {/* This is added for any error descriptions */}
108
115
  {!!(identifierFieldMeta.touched && identifierFieldMeta.error) && (
109
116
  <span className={styles.dangerLabel01}>{identifierFieldMeta.error && t(identifierFieldMeta.error)}</span>
110
117
  )}
111
118
  </div>
112
119
  )}
113
- <div style={{ marginBottom: '1rem' }}>
114
- {!patientIdentifier.required && patientIdentifier.initialValue && hideInputField && (
120
+ <div className={styles.actionButtonContainer}>
121
+ {showEditButton && (
115
122
  <UserHasAccess privilege="Edit Patient Identifiers">
116
123
  <Button
124
+ data-testid="edit-button"
117
125
  size="md"
118
126
  kind="ghost"
119
127
  onClick={handleEdit}
@@ -124,7 +132,7 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
124
132
  </Button>
125
133
  </UserHasAccess>
126
134
  )}
127
- {initialValue && initialValue !== identifierValue && (
135
+ {showResetButton && (
128
136
  <UserHasAccess privilege="Edit Patient Identifiers">
129
137
  <Button
130
138
  size="md"
@@ -1,29 +1,28 @@
1
1
  /* eslint-disable testing-library/no-node-access */
2
2
  import React from 'react';
3
3
  import { render, screen } from '@testing-library/react';
4
- import { Formik, Form } from 'formik';
5
- import { type PatientIdentifierType } from '../../../patient-registration.types';
6
- import { initialFormValues } from '../../../patient-registration.component';
4
+ import { Form, Formik } from 'formik';
5
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
6
+ import { esmPatientRegistrationSchema, type RegistrationConfig } from '../../../../config-schema';
7
+ import { ResourcesContext, type Resources } from '../../../../offline.resources';
8
+ import {
9
+ PatientRegistrationContext,
10
+ type PatientRegistrationContextProps,
11
+ } from '../../../patient-registration-context';
12
+ import type {
13
+ AddressTemplate,
14
+ FormValues,
15
+ IdentifierSource,
16
+ PatientIdentifierValue,
17
+ } from '../../../patient-registration.types';
7
18
  import IdentifierInput from './identifier-input.component';
19
+ import userEvent from '@testing-library/user-event';
8
20
 
9
- // TODO: Fix this test
10
- xdescribe('identifier input', () => {
11
- const openmrsID = {
12
- name: 'OpenMRS ID',
21
+ const mockIdentifierTypes = [
22
+ {
13
23
  fieldName: 'openMrsId',
14
- required: true,
15
- uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
16
- format: null,
17
- isPrimary: true,
24
+ format: '',
18
25
  identifierSources: [
19
- {
20
- uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
21
- name: 'Generator 1 for OpenMRS ID',
22
- autoGenerationOption: {
23
- manualEntryEnabled: false,
24
- automaticGenerationEnabled: true,
25
- },
26
- },
27
26
  {
28
27
  uuid: '01af8526-cea4-4175-aa90-340acb411771',
29
28
  name: 'Generator 2 for OpenMRS ID',
@@ -33,72 +32,172 @@ xdescribe('identifier input', () => {
33
32
  },
34
33
  },
35
34
  ],
36
- autoGenerationSource: null,
37
- };
35
+ isPrimary: true,
36
+ name: 'OpenMRS ID',
37
+ required: true,
38
+ uniquenessBehavior: 'UNIQUE' as const,
39
+ uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
40
+ },
41
+ ];
38
42
 
39
- const setupIdentifierInput = async (identifierType: PatientIdentifierType) => {
40
- initialFormValues['source-for-' + identifierType.fieldName] = identifierType.identifierSources[0].name;
43
+ const mockResourcesContextValue: Resources = {
44
+ addressTemplate: {} as AddressTemplate,
45
+ currentSession: {
46
+ authenticated: true,
47
+ sessionId: 'JSESSION',
48
+ currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
49
+ },
50
+ relationshipTypes: [],
51
+ identifierTypes: [...mockIdentifierTypes],
52
+ };
41
53
 
42
- render(
43
- <Formik initialValues={initialFormValues} onSubmit={null}>
44
- <Form>
45
- <IdentifierInput identifierType={identifierType} />
46
- </Form>
47
- </Formik>,
48
- );
49
- const identifierInput = screen.getByLabelText(identifierType.fieldName) as HTMLInputElement;
50
- let identifierSourceSelectInput = screen.getByLabelText('source-for-' + identifierType.fieldName);
51
- return {
52
- identifierInput,
53
- identifierSourceSelectInput,
54
- };
55
- };
54
+ const mockContextValues: PatientRegistrationContextProps = {
55
+ currentPhoto: '',
56
+ inEditMode: false,
57
+ identifierTypes: [],
58
+ initialFormValues: {} as FormValues,
59
+ isOffline: false,
60
+ setCapturePhotoProps: jest.fn(),
61
+ setFieldValue: jest.fn(),
62
+ setInitialFormValues: jest.fn(),
63
+ setFieldTouched: jest.fn(),
64
+ validationSchema: null,
65
+ values: {} as FormValues,
66
+ };
56
67
 
57
- it('exists', async () => {
58
- const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
68
+ const mockUseConfig = jest.mocked(useConfig<RegistrationConfig>);
59
69
 
60
- expect(identifierInput.type).toBe('text');
61
- expect(identifierSourceSelectInput.type).toBe('select-one');
70
+ describe('identifier input', () => {
71
+ mockUseConfig.mockReturnValue({
72
+ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema),
62
73
  });
63
74
 
64
- it('has correct props for identifier source select input', async () => {
65
- const { identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
75
+ const fieldName = 'openMrsId';
76
+ const openmrsID = {
77
+ identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
78
+ initialValue: '',
79
+ identifierName: 'OpenMRS ID',
80
+ selectedSource: {
81
+ uuid: '01af8526-cea4-4175-aa90-340acb411771',
82
+ name: 'Generator 2 for OpenMRS ID',
83
+ autoGenerationOption: {
84
+ manualEntryEnabled: false,
85
+ automaticGenerationEnabled: true,
86
+ },
87
+ } as IdentifierSource,
88
+ autoGeneration: false,
89
+ preferred: true,
90
+ required: true,
91
+ } as PatientIdentifierValue;
92
+
93
+ const setupIdentifierInput = (patientIdentifier: PatientIdentifierValue, initialValues = {}) => {
94
+ render(
95
+ <ResourcesContext.Provider value={mockResourcesContextValue}>
96
+ <Formik initialValues={initialValues} onSubmit={jest.fn()}>
97
+ <Form>
98
+ <PatientRegistrationContext.Provider value={mockContextValues}>
99
+ <IdentifierInput patientIdentifier={patientIdentifier} fieldName={fieldName} />
100
+ </PatientRegistrationContext.Provider>
101
+ </Form>
102
+ </Formik>
103
+ </ResourcesContext.Provider>,
104
+ );
105
+ };
66
106
 
67
- expect(identifierSourceSelectInput.childElementCount).toBe(3);
68
- expect(identifierSourceSelectInput.value).toBe('Generator 1 for OpenMRS ID');
107
+ it('shows the identifier input', () => {
108
+ openmrsID.autoGeneration = false;
109
+ setupIdentifierInput(openmrsID as PatientIdentifierValue);
110
+ expect(screen.getByLabelText(openmrsID.identifierName)).toBeInTheDocument();
69
111
  });
70
112
 
71
- it('has correct props for identifier input', async () => {
72
- const { identifierInput } = await setupIdentifierInput(openmrsID);
73
- expect(identifierInput.placeholder).toBe('Auto-generated');
74
- expect(identifierInput.disabled).toBe(true);
113
+ it('displays an edit button when there is an initial value', async () => {
114
+ // setup
115
+ openmrsID.autoGeneration = false;
116
+ openmrsID.required = false;
117
+ openmrsID.initialValue = '1002UU9';
118
+ openmrsID.identifierValue = '1002UU9';
119
+ // replay
120
+ setupIdentifierInput(openmrsID as PatientIdentifierValue);
121
+ expect(screen.getByText('Edit')).toBeInTheDocument();
75
122
  });
76
123
 
77
- it('text input should not be disabled if manual entry is enabled', async () => {
124
+ it('hides the edit button when the identifier is required', async () => {
78
125
  // setup
79
- openmrsID.identifierSources[0].autoGenerationOption.manualEntryEnabled = true;
126
+ openmrsID.autoGeneration = false;
127
+ openmrsID.required = true;
128
+ openmrsID.initialValue = '1002UU9';
129
+ openmrsID.identifierValue = '1002UU9';
80
130
  // replay
81
- const { identifierInput } = await setupIdentifierInput(openmrsID);
82
- expect(identifierInput.placeholder).toBe('Auto-generated');
83
- expect(identifierInput.disabled).toBe(false);
131
+ setupIdentifierInput(openmrsID);
132
+ expect(screen.queryByText('Edit')).not.toBeInTheDocument();
84
133
  });
85
134
 
86
- it('should not render select widget if auto-entry is false', async () => {
135
+ it('displays a delete button when the identifier is not a default type', () => {
87
136
  // setup
88
- openmrsID.identifierSources = [
89
- {
90
- uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
91
- name: 'Generator 1 for OpenMRS ID',
137
+ openmrsID.required = false;
138
+ // replay
139
+ setupIdentifierInput(openmrsID);
140
+ expect(screen.getByText('Delete')).toBeInTheDocument();
141
+ });
142
+
143
+ describe('auto-generated identifier', () => {
144
+ it('hides the input when the identifier is auto-generated', () => {
145
+ openmrsID.autoGeneration = true;
146
+ setupIdentifierInput(openmrsID);
147
+ expect(screen.getByTestId('identifier-input')).toHaveAttribute('type', 'hidden');
148
+ });
149
+
150
+ it("displays 'Auto-Generated' when the indentifier has auto generation", () => {
151
+ openmrsID.autoGeneration = true;
152
+ setupIdentifierInput(openmrsID);
153
+ expect(screen.getByTestId('identifier-placeholder').innerHTML).toBe('Auto-generated');
154
+ expect(screen.getByTestId('identifier-input')).toBeDisabled();
155
+ });
156
+
157
+ describe('manual entry allowed', () => {
158
+ openmrsID.selectedSource = {
92
159
  autoGenerationOption: {
93
160
  manualEntryEnabled: true,
94
- automaticGenerationEnabled: false,
95
161
  },
96
- },
97
- ];
98
- // replay
99
- const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
100
- expect(identifierInput.placeholder).toBe('Enter identifier');
101
- expect(identifierInput.disabled).toBe(false);
102
- expect(identifierSourceSelectInput).toBe(undefined);
162
+ } as IdentifierSource;
163
+
164
+ it('shows the edit button', () => {
165
+ openmrsID.autoGeneration = true;
166
+ setupIdentifierInput(openmrsID);
167
+ expect(screen.getByText('Edit')).toBeInTheDocument();
168
+ });
169
+
170
+ describe('edit button clicked', () => {
171
+ it('displays an empty input field', async () => {
172
+ const user = userEvent.setup();
173
+ openmrsID.autoGeneration = true;
174
+ openmrsID.required = false;
175
+ openmrsID.selectedSource = {
176
+ autoGenerationOption: {
177
+ manualEntryEnabled: true,
178
+ },
179
+ } as IdentifierSource;
180
+ setupIdentifierInput(openmrsID);
181
+ const editButton = screen.getByTestId('edit-button');
182
+ await user.click(editButton);
183
+ expect(screen.getByLabelText(new RegExp(`${openmrsID.identifierName}`))).toHaveValue('');
184
+ });
185
+
186
+ it('displays an input field with the identifier value if it exists', async () => {
187
+ const user = userEvent.setup();
188
+ openmrsID.autoGeneration = true;
189
+ openmrsID.required = false;
190
+ openmrsID.selectedSource = {
191
+ autoGenerationOption: {
192
+ manualEntryEnabled: true,
193
+ },
194
+ } as IdentifierSource;
195
+ setupIdentifierInput(openmrsID, { identifiers: { [fieldName]: { identifierValue: '10001V' } } });
196
+ const editButton = screen.getByTestId('edit-button');
197
+ await user.click(editButton);
198
+ expect(screen.getByLabelText(new RegExp(`${openmrsID.identifierName}`))).toHaveValue('10001V');
199
+ });
200
+ });
201
+ });
103
202
  });
104
203
  });
@@ -25,7 +25,10 @@ export const dummyFormValues: FormValues = {
25
25
  telephoneNumber: '0800001066',
26
26
  isDead: false,
27
27
  deathDate: '',
28
+ deathTime: '',
29
+ deathTimeFormat: 'AM',
28
30
  deathCause: '',
31
+ nonCodedCauseOfDeath: '',
29
32
  relationships: [],
30
33
  address: {
31
34
  address1: 'Bom Jesus Street',
@@ -116,3 +116,8 @@
116
116
  margin: 0;
117
117
  padding-left: layout.$spacing-05;
118
118
  }
119
+
120
+ .actionButtonContainer {
121
+ margin-left: layout.$spacing-05;
122
+ margin-bottom: layout.$spacing-05;
123
+ }
@@ -1,7 +1,7 @@
1
1
  import { useConfig } from '@openmrs/esm-framework';
2
2
  import { createContext, type SetStateAction } from 'react';
3
3
  import { type RegistrationConfig } from '../config-schema';
4
- import { type FormValues, type CapturePhotoProps } from './patient-registration.types';
4
+ import { type CapturePhotoProps, type FormValues } from './patient-registration.types';
5
5
 
6
6
  export interface PatientRegistrationContextProps {
7
7
  currentPhoto: string;
@@ -9,11 +9,12 @@ export interface PatientRegistrationContextProps {
9
9
  inEditMode: boolean;
10
10
  initialFormValues: FormValues;
11
11
  isOffline: boolean;
12
- setCapturePhotoProps(value: SetStateAction<CapturePhotoProps>): void;
13
- setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
14
12
  setInitialFormValues?: React.Dispatch<SetStateAction<FormValues>>;
15
13
  validationSchema: any;
16
14
  values: FormValues;
15
+ setCapturePhotoProps(value: SetStateAction<CapturePhotoProps>): void;
16
+ setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
17
+ setFieldTouched(field: string, isTouched?: any, shouldValidate?: boolean): void;
17
18
  }
18
19
 
19
20
  export const PatientRegistrationContext = createContext<PatientRegistrationContextProps | undefined>(undefined);
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  type FetchResponse,
3
- type OpenmrsResource,
4
3
  getSynchronizationItems,
5
4
  openmrsFetch,
5
+ type OpenmrsResource,
6
+ restBaseUrl,
6
7
  useConfig,
7
8
  usePatient,
8
- restBaseUrl,
9
9
  } from '@openmrs/esm-framework';
10
10
  import last from 'lodash-es/last';
11
11
  import camelCase from 'lodash-es/camelCase';
@@ -14,14 +14,17 @@ import useSWR from 'swr';
14
14
  import { v4 } from 'uuid';
15
15
  import { type RegistrationConfig } from '../config-schema';
16
16
  import { patientRegistration } from '../constants';
17
- import { useConceptAnswers, useGlobalProperties } from '../patient-verification/patient-verification-hook';
18
17
  import {
18
+ useConceptAnswers,
19
+ useGlobalProperties,
20
+ } from '../client-registry/patient-verification/patient-verification-hook';
21
+ import {
22
+ type Encounter,
19
23
  type FormValues,
24
+ type PatientIdentifierResponse,
20
25
  type PatientRegistration,
21
26
  type PatientUuidMapType,
22
27
  type PersonAttributeResponse,
23
- type PatientIdentifierResponse,
24
- type Encounter,
25
28
  type ConceptAnswers,
26
29
  type ObsResponse,
27
30
  } from './patient-registration.types';
@@ -36,8 +39,10 @@ import { useInitialPatientRelationships } from './section/patient-relationships/
36
39
  import dayjs from 'dayjs';
37
40
 
38
41
  export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
42
+ const { freeTextFieldConceptUuid } = useConfig<RegistrationConfig>();
39
43
  const { martialStatus, education, occupation, educationLoad } = useConcepts();
40
44
  const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
45
+ const { data: deathInfo, isLoading: isLoadingDeathInfo } = useInitialPersonDeathInfo(patientUuid);
41
46
  const { data: attributes, isLoading: isLoadingAttributes } = useInitialPersonAttributes(patientUuid);
42
47
  const { data: identifiers, isLoading: isLoadingIdentifiers } = useInitialPatientIdentifiers(patientUuid);
43
48
  const { data: relationships, isLoading: isLoadingRelationships } = useInitialPatientRelationships(patientUuid);
@@ -59,8 +64,11 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
59
64
  birthdateEstimated: false,
60
65
  telephoneNumber: '',
61
66
  isDead: false,
62
- deathDate: '',
67
+ deathDate: undefined,
68
+ deathTime: undefined,
69
+ deathTimeFormat: 'AM',
63
70
  deathCause: '',
71
+ nonCodedCauseOfDeath: '',
64
72
  relationships: [],
65
73
  identifiers: {},
66
74
  address: {},
@@ -101,6 +109,24 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
101
109
  })();
102
110
  }, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
103
111
 
112
+ // Set initial patient death info
113
+ useEffect(() => {
114
+ if (!isLoadingDeathInfo && deathInfo?.dead) {
115
+ const deathDatetime = deathInfo.deathDate || null;
116
+ const deathDate = deathDatetime ? new Date(deathDatetime) : undefined;
117
+ const time = deathDate ? dayjs(deathDate).format('hh:mm') : undefined;
118
+ const timeFormat = deathDate ? (dayjs(deathDate).hour() >= 12 ? 'PM' : 'AM') : 'AM';
119
+ setInitialFormValues((initialFormValues) => ({
120
+ ...initialFormValues,
121
+ isDead: deathInfo.dead || false,
122
+ deathDate: deathDate,
123
+ deathTime: time,
124
+ deathTimeFormat: timeFormat,
125
+ deathCause: deathInfo.causeOfDeathNonCoded ? freeTextFieldConceptUuid : deathInfo.causeOfDeath?.uuid,
126
+ nonCodedCauseOfDeath: deathInfo.causeOfDeathNonCoded,
127
+ }));
128
+ }
129
+ }, [isLoadingDeathInfo, deathInfo, setInitialFormValues]);
104
130
  // Setting authentication token
105
131
 
106
132
  useEffect(() => {
@@ -301,6 +327,32 @@ function useInitialPersonAttributes(personUuid: string) {
301
327
  return result;
302
328
  }
303
329
 
330
+ interface DeathInfoResults {
331
+ uuid: string;
332
+ display: string;
333
+ causeOfDeath: OpenmrsResource | null;
334
+ dead: boolean;
335
+ deathDate: string;
336
+ causeOfDeathNonCoded: string | null;
337
+ }
338
+
339
+ function useInitialPersonDeathInfo(personUuid: string) {
340
+ const { data, error, isLoading } = useSWR<FetchResponse<DeathInfoResults>, Error>(
341
+ !!personUuid
342
+ ? `${restBaseUrl}/person/${personUuid}?v=custom:(uuid,display,causeOfDeath,dead,deathDate,causeOfDeathNonCoded)`
343
+ : null,
344
+ openmrsFetch,
345
+ );
346
+
347
+ const result = useMemo(() => {
348
+ return {
349
+ data: data?.data,
350
+ isLoading,
351
+ };
352
+ }, [data, error]);
353
+ return result;
354
+ }
355
+
304
356
  function getPatientAttributeUuidMapForPatient(attributes: Array<PersonAttributeResponse>) {
305
357
  const attributeUuidMap = {};
306
358
  attributes.forEach((attribute) => {
@@ -356,9 +408,15 @@ function useConcepts() {
356
408
  },
357
409
  ];
358
410
 
359
- const martialStatus: Array<ConceptAnswers> = config.fieldDefinitions
360
- .find((fieldDefinition) => fieldDefinition.id === 'maritalStatus')
361
- .customConceptAnswers.map((concept) => ({ uuid: concept.uuid, display: concept.label }));
411
+ const maritalStatusCustomConceptAnswers =
412
+ config.fieldDefinitions.find((fieldDefinition) => fieldDefinition.id === 'maritalStatus')?.customConceptAnswers ??
413
+ [];
414
+
415
+ const martialStatus: Array<ConceptAnswers> =
416
+ maritalStatusCustomConceptAnswers.map((concept) => ({
417
+ uuid: concept?.uuid,
418
+ display: concept?.label,
419
+ })) ?? [];
362
420
 
363
421
  return { martialStatus, education, occupation, educationLoad };
364
422
  }
@@ -3,11 +3,11 @@ import camelCase from 'lodash-es/camelCase';
3
3
  import { parseDate } from '@openmrs/esm-framework';
4
4
  import {
5
5
  type AddressValidationSchemaType,
6
+ type Encounter,
6
7
  type FormValues,
7
8
  type PatientIdentifier,
8
- type PatientUuidMapType,
9
9
  type PatientIdentifierValue,
10
- type Encounter,
10
+ type PatientUuidMapType,
11
11
  } from './patient-registration.types';
12
12
 
13
13
  export function parseAddressTemplateXml(addressTemplate: string) {
@@ -47,6 +47,7 @@ export function parseAddressTemplateXml(addressTemplate: string) {
47
47
  addressValidationSchema,
48
48
  };
49
49
  }
50
+
50
51
  export function parseAddressTemplateXmlOld(addressTemplate: string) {
51
52
  const templateXmlDoc = new DOMParser().parseFromString(addressTemplate, 'text/xml');
52
53
  const nameMappings = templateXmlDoc.querySelector('nameMappings').querySelectorAll('property');
@@ -123,11 +124,6 @@ export function getFormValuesFromFhirPatient(patient: fhir.Patient) {
123
124
  result.birthdate = patient.birthDate ? parseDate(patient.birthDate) : undefined;
124
125
  result.telephoneNumber = patient.telecom ? patient.telecom[0].value : '';
125
126
 
126
- if (patient.deceasedBoolean || patient.deceasedDateTime) {
127
- result.isDead = true;
128
- result.deathDate = patient.deceasedDateTime ? patient.deceasedDateTime.split('T')[0] : '';
129
- }
130
-
131
127
  return {
132
128
  ...result,
133
129
  ...patient.identifier.map((identifier) => {