@kenyaemr/esm-patient-registration-app 8.1.1-pre.111 → 8.1.1-pre.116

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 (101) hide show
  1. package/.turbo/turbo-build.log +22 -23
  2. package/dist/108.js +1 -1
  3. package/dist/130.js +1 -1
  4. package/dist/130.js.LICENSE.txt +2 -0
  5. package/dist/130.js.map +1 -1
  6. package/dist/2.js +1 -0
  7. package/dist/2.js.map +1 -0
  8. package/dist/250.js +1 -0
  9. package/dist/250.js.map +1 -0
  10. package/dist/271.js +1 -1
  11. package/dist/319.js +1 -1
  12. package/dist/325.js +1 -0
  13. package/dist/325.js.map +1 -0
  14. package/dist/372.js +2 -0
  15. package/dist/372.js.map +1 -0
  16. package/dist/460.js +1 -1
  17. package/dist/471.js +1 -0
  18. package/dist/471.js.map +1 -0
  19. package/dist/574.js +1 -1
  20. package/dist/644.js +1 -1
  21. package/dist/662.js +1 -0
  22. package/dist/662.js.map +1 -0
  23. package/dist/757.js +1 -1
  24. package/dist/76.js +1 -1
  25. package/dist/788.js +1 -1
  26. package/dist/807.js +1 -1
  27. package/dist/833.js +1 -1
  28. package/dist/895.js +2 -0
  29. package/dist/{745.js.LICENSE.txt → 895.js.LICENSE.txt} +10 -0
  30. package/dist/895.js.map +1 -0
  31. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  32. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +150 -177
  33. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  34. package/dist/main.js +1 -1
  35. package/dist/main.js.map +1 -1
  36. package/dist/routes.json +1 -1
  37. package/package.json +2 -2
  38. package/src/config-schema.ts +28 -2
  39. package/src/index.ts +1 -4
  40. package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +98 -0
  41. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +84 -0
  42. package/src/patient-registration/field/dob/dob.component.tsx +21 -7
  43. package/src/patient-registration/field/field.component.tsx +11 -5
  44. package/src/patient-registration/field/field.resource.ts +11 -4
  45. package/src/patient-registration/field/field.scss +23 -1
  46. package/src/patient-registration/field/gender/gender-field.component.tsx +2 -1
  47. package/src/patient-registration/field/gender/gender-field.test.tsx +1 -0
  48. package/src/patient-registration/field/name/name-field.component.tsx +5 -1
  49. package/src/patient-registration/form-manager.test.ts +3 -0
  50. package/src/patient-registration/form-manager.ts +30 -15
  51. package/src/patient-registration/input/basic-input/input/input.component.tsx +5 -1
  52. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +2 -2
  53. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +122 -71
  54. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +3 -0
  55. package/src/patient-registration/patient-registration-context.ts +4 -3
  56. package/src/patient-registration/patient-registration-hooks.ts +63 -8
  57. package/src/patient-registration/patient-registration-utils.ts +3 -7
  58. package/src/patient-registration/patient-registration.component.tsx +20 -13
  59. package/src/patient-registration/patient-registration.resource.ts +8 -0
  60. package/src/patient-registration/patient-registration.test.tsx +9 -3
  61. package/src/patient-registration/patient-registration.types.ts +4 -1
  62. package/src/patient-registration/section/death-info/death-info-section.component.tsx +22 -17
  63. package/src/patient-registration/section/death-info/death-info-section.test.tsx +4 -14
  64. package/src/patient-registration/section/section.component.tsx +1 -1
  65. package/src/patient-registration/section/section.scss +5 -0
  66. package/src/patient-registration/validation/{patient-registration-validation.test.tsx → patient-registration-validation.test.ts} +26 -4
  67. package/src/patient-registration/validation/patient-registration-validation.ts +126 -0
  68. package/src/routes.json +10 -18
  69. package/src/widgets/cancel-patient-edit.modal.tsx +33 -0
  70. package/src/widgets/cancel-patient-edit.test.tsx +2 -3
  71. package/src/widgets/delete-identifier-confirmation.modal.tsx +22 -15
  72. package/src/widgets/delete-identifier-confirmation.test.tsx +2 -1
  73. package/translations/am.json +36 -25
  74. package/translations/ar.json +37 -26
  75. package/translations/en.json +37 -20
  76. package/translations/es.json +38 -26
  77. package/translations/fr.json +47 -35
  78. package/translations/he.json +37 -30
  79. package/translations/km.json +37 -30
  80. package/translations/zh.json +37 -20
  81. package/translations/zh_CN.json +37 -20
  82. package/dist/152.js +0 -1
  83. package/dist/152.js.map +0 -1
  84. package/dist/255.js +0 -2
  85. package/dist/255.js.map +0 -1
  86. package/dist/303.js +0 -1
  87. package/dist/303.js.map +0 -1
  88. package/dist/623.js +0 -1
  89. package/dist/623.js.map +0 -1
  90. package/dist/729.js +0 -1
  91. package/dist/729.js.map +0 -1
  92. package/dist/735.js +0 -1
  93. package/dist/735.js.map +0 -1
  94. package/dist/745.js +0 -2
  95. package/dist/745.js.map +0 -1
  96. package/dist/830.js +0 -1
  97. package/dist/830.js.map +0 -1
  98. package/src/patient-registration/validation/patient-registration-validation.tsx +0 -60
  99. package/src/widgets/cancel-patient-edit.component.tsx +0 -37
  100. package/src/widgets/delete-identifier-confirmation.scss +0 -34
  101. /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
@@ -100,10 +100,10 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
100
100
  ) : (
101
101
  <div className={styles.textID}>
102
102
  <p className={styles.label}>{identifierName}</p>
103
- <p className={styles.bodyShort02}>
103
+ <p data-testid="identifier-label" className={styles.bodyShort02}>
104
104
  {autoGeneration ? t('autoGeneratedPlaceholderText', 'Auto-generated') : identifierValue}
105
105
  </p>
106
- <input type="hidden" {...identifierField} disabled />
106
+ <input data-testid="identifier-input" type="hidden" {...identifierField} disabled />
107
107
  {/* This is added for any error descriptions */}
108
108
  {!!(identifierFieldMeta.touched && identifierFieldMeta.error) && (
109
109
  <span className={styles.dangerLabel01}>{identifierFieldMeta.error && t(identifierFieldMeta.error)}</span>
@@ -1,29 +1,31 @@
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 { ResourcesContext, type Resources } from '../../../../offline.resources';
6
+ import {
7
+ PatientRegistrationContext,
8
+ type PatientRegistrationContextProps,
9
+ } from '../../../patient-registration-context';
10
+ import type { AddressTemplate, FormValues, PatientIdentifierValue } from '../../../patient-registration.types';
7
11
  import IdentifierInput from './identifier-input.component';
12
+ import userEvent from '@testing-library/user-event';
8
13
 
9
- // TODO: Fix this test
10
- xdescribe('identifier input', () => {
11
- const openmrsID = {
12
- name: 'OpenMRS ID',
14
+ const predefinedAddressTemplate = {
15
+ uuid: 'test-address-template-uuid',
16
+ property: 'layout.address.format',
17
+ description: 'Test Address Template',
18
+ display:
19
+ 'Layout - Address Format = <org.openmrs.layout.address.AddressTemplate>\n <nameMappings class="properties">\n <property name="postalCode" value="Location.postalCode"/>\n <property name="address2" value="Location.address2"/>\n <property name="address1" value="Location.address1"/>\n <property name="country" value="Location.country"/>\n <property name="stateProvince" value="Location.stateProvince"/>\n <property name="cityVillage" value="Location.cityVillage"/>\n </nameMappings>\n <sizeMappings class="properties">\n <property name="postalCode" value="10"/>\n <property name="address2" value="40"/>\n <property name="address1" value="40"/>\n <property name="country" value="10"/>\n <property name="stateProvince" value="10"/>\n <property name="cityVillage" value="10"/>\n </sizeMappings>\n <lineByLineFormat>\n <string>address1</string>\n <string>address2</string>\n <string>cityVillage stateProvince country postalCode</string>\n </lineByLineFormat>\n </org.openmrs.layout.address.AddressTemplate>',
20
+ value:
21
+ '<org.openmrs.layout.address.AddressTemplate>\r\n <nameMappings class="properties">\r\n <property name="postalCode" value="Location.postalCode"/>\r\n <property name="address2" value="Location.address2"/>\r\n <property name="address1" value="Location.address1"/>\r\n <property name="country" value="Location.country"/>\r\n <property name="stateProvince" value="Location.stateProvince"/>\r\n <property name="cityVillage" value="Location.cityVillage"/>\r\n </nameMappings>\r\n <sizeMappings class="properties">\r\n <property name="postalCode" value="4"/>\r\n <property name="address1" value="40"/>\r\n <property name="address2" value="40"/>\r\n <property name="country" value="10"/>\r\n <property name="stateProvince" value="10"/>\r\n <property name="cityVillage" value="10"/>\r\n <asset name="cityVillage" value="10"/>\r\n </sizeMappings>\r\n <lineByLineFormat>\r\n <string>address1 address2</string>\r\n <string>cityVillage stateProvince postalCode</string>\r\n <string>country</string>\r\n </lineByLineFormat>\r\n <elementDefaults class="properties">\r\n <property name="country" value=""/>\r\n </elementDefaults>\r\n <elementRegex class="properties">\r\n <property name="address1" value="[a-zA-Z]+$"/>\r\n </elementRegex>\r\n <elementRegexFormats class="properties">\r\n <property name="address1" value="Countries can only be letters"/>\r\n </elementRegexFormats>\r\n </org.openmrs.layout.address.AddressTemplate>',
22
+ };
23
+
24
+ const mockIdentifierTypes = [
25
+ {
13
26
  fieldName: 'openMrsId',
14
- required: true,
15
- uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
16
- format: null,
17
- isPrimary: true,
27
+ format: '',
18
28
  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
29
  {
28
30
  uuid: '01af8526-cea4-4175-aa90-340acb411771',
29
31
  name: 'Generator 2 for OpenMRS ID',
@@ -33,72 +35,121 @@ xdescribe('identifier input', () => {
33
35
  },
34
36
  },
35
37
  ],
36
- autoGenerationSource: null,
37
- };
38
+ isPrimary: true,
39
+ name: 'OpenMRS ID',
40
+ required: true,
41
+ uniquenessBehavior: 'UNIQUE' as const,
42
+ uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
43
+ },
44
+ ];
38
45
 
39
- const setupIdentifierInput = async (identifierType: PatientIdentifierType) => {
40
- initialFormValues['source-for-' + identifierType.fieldName] = identifierType.identifierSources[0].name;
46
+ const mockResourcesContextValue: Resources = {
47
+ addressTemplate: predefinedAddressTemplate as unknown as AddressTemplate,
48
+ currentSession: {
49
+ authenticated: true,
50
+ sessionId: 'JSESSION',
51
+ currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
52
+ },
53
+ relationshipTypes: [],
54
+ identifierTypes: [...mockIdentifierTypes],
55
+ };
41
56
 
57
+ const mockContextValues: PatientRegistrationContextProps = {
58
+ currentPhoto: '',
59
+ inEditMode: false,
60
+ identifierTypes: [],
61
+ initialFormValues: {} as FormValues,
62
+ isOffline: false,
63
+ setCapturePhotoProps: jest.fn(),
64
+ setFieldValue: jest.fn(),
65
+ setInitialFormValues: jest.fn(),
66
+ setFieldTouched: jest.fn(),
67
+ validationSchema: null,
68
+ values: {} as FormValues,
69
+ };
70
+
71
+ describe('identifier input', () => {
72
+ const fieldName = 'openMrsId';
73
+ const openmrsID = {
74
+ identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
75
+ initialValue: '',
76
+ identifierValue: '',
77
+ identifierName: 'OpenMRS ID',
78
+ selectedSource: {
79
+ uuid: '01af8526-cea4-4175-aa90-340acb411771',
80
+ name: 'Generator 2 for OpenMRS ID',
81
+ autoGenerationOption: {
82
+ manualEntryEnabled: true,
83
+ automaticGenerationEnabled: true,
84
+ },
85
+ },
86
+ autoGeneration: false,
87
+ preferred: true,
88
+ required: true,
89
+ };
90
+
91
+ const setupIdentifierInput = async (patientIdentifier: PatientIdentifierValue) => {
42
92
  render(
43
- <Formik initialValues={initialFormValues} onSubmit={null}>
44
- <Form>
45
- <IdentifierInput identifierType={identifierType} />
46
- </Form>
47
- </Formik>,
93
+ <ResourcesContext.Provider value={mockResourcesContextValue}>
94
+ <Formik initialValues={{}} onSubmit={jest.fn()}>
95
+ <Form>
96
+ <PatientRegistrationContext.Provider value={mockContextValues}>
97
+ <IdentifierInput patientIdentifier={patientIdentifier} fieldName={fieldName} />
98
+ </PatientRegistrationContext.Provider>
99
+ </Form>
100
+ </Formik>
101
+ </ResourcesContext.Provider>,
48
102
  );
49
- const identifierInput = screen.getByLabelText(identifierType.fieldName) as HTMLInputElement;
50
- let identifierSourceSelectInput = screen.getByLabelText('source-for-' + identifierType.fieldName);
103
+
104
+ let identifierLabel: HTMLParagraphElement;
105
+ let identifierInput: HTMLInputElement;
106
+ if (patientIdentifier.autoGeneration) {
107
+ identifierLabel = screen.getByTestId('identifier-label');
108
+ identifierInput = screen.getByTestId('identifier-input');
109
+ } else {
110
+ identifierLabel = screen.getByText(patientIdentifier.identifierName);
111
+ identifierInput = screen.getByLabelText(patientIdentifier.identifierName);
112
+ }
51
113
  return {
114
+ identifierLabel,
52
115
  identifierInput,
53
- identifierSourceSelectInput,
54
116
  };
55
117
  };
56
118
 
57
- it('exists', async () => {
58
- const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
59
-
60
- expect(identifierInput.type).toBe('text');
61
- expect(identifierSourceSelectInput.type).toBe('select-one');
119
+ it('shows the identifier input', async () => {
120
+ const { identifierInput } = await setupIdentifierInput(openmrsID);
121
+ expect(identifierInput).toBeInTheDocument();
62
122
  });
63
123
 
64
- it('has correct props for identifier source select input', async () => {
65
- const { identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
124
+ describe('Auto-generated identifier', () => {
125
+ openmrsID.autoGeneration = true;
66
126
 
67
- expect(identifierSourceSelectInput.childElementCount).toBe(3);
68
- expect(identifierSourceSelectInput.value).toBe('Generator 1 for OpenMRS ID');
69
- });
127
+ it('hides the input when the identifier is auto-generated', async () => {
128
+ const { identifierInput } = await setupIdentifierInput(openmrsID);
129
+ expect(identifierInput.type).toBe('hidden');
130
+ });
70
131
 
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);
75
- });
132
+ it("displays 'Auto-Generated' when the indentifier has auto generation", async () => {
133
+ const { identifierLabel, identifierInput } = await setupIdentifierInput(openmrsID);
134
+ expect(identifierLabel.innerHTML).toBe('Auto-generated');
135
+ expect(identifierInput.disabled).toBe(true);
136
+ });
76
137
 
77
- it('text input should not be disabled if manual entry is enabled', async () => {
78
- // setup
79
- openmrsID.identifierSources[0].autoGenerationOption.manualEntryEnabled = true;
80
- // replay
81
- const { identifierInput } = await setupIdentifierInput(openmrsID);
82
- expect(identifierInput.placeholder).toBe('Auto-generated');
83
- expect(identifierInput.disabled).toBe(false);
84
- });
138
+ it('displays an edit button when there is an initial value', async () => {
139
+ // setup
140
+ openmrsID.required = false;
141
+ openmrsID.initialValue = '1002UU9';
142
+ // replay
143
+ await setupIdentifierInput(openmrsID);
144
+ expect(screen.getByText('Edit')).toBeInTheDocument();
145
+ });
85
146
 
86
- it('should not render select widget if auto-entry is false', async () => {
87
- // setup
88
- openmrsID.identifierSources = [
89
- {
90
- uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
91
- name: 'Generator 1 for OpenMRS ID',
92
- autoGenerationOption: {
93
- manualEntryEnabled: true,
94
- automaticGenerationEnabled: false,
95
- },
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);
147
+ it('displays a delete button when the identifier is not a default type', async () => {
148
+ // setup
149
+ openmrsID.required = false;
150
+ // replay
151
+ await setupIdentifierInput(openmrsID);
152
+ expect(screen.getByText('Delete')).toBeInTheDocument();
153
+ });
103
154
  });
104
155
  });
@@ -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',
@@ -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';
@@ -19,12 +19,12 @@ import {
19
19
  useGlobalProperties,
20
20
  } from '../client-registry/patient-verification/patient-verification-hook';
21
21
  import {
22
+ type Encounter,
22
23
  type FormValues,
24
+ type PatientIdentifierResponse,
23
25
  type PatientRegistration,
24
26
  type PatientUuidMapType,
25
27
  type PersonAttributeResponse,
26
- type PatientIdentifierResponse,
27
- type Encounter,
28
28
  type ConceptAnswers,
29
29
  type ObsResponse,
30
30
  } from './patient-registration.types';
@@ -39,8 +39,10 @@ import { useInitialPatientRelationships } from './section/patient-relationships/
39
39
  import dayjs from 'dayjs';
40
40
 
41
41
  export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
42
+ const { freeTextFieldConceptUuid } = useConfig<RegistrationConfig>();
42
43
  const { martialStatus, education, occupation, educationLoad } = useConcepts();
43
44
  const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
45
+ const { data: deathInfo, isLoading: isLoadingDeathInfo } = useInitialPersonDeathInfo(patientUuid);
44
46
  const { data: attributes, isLoading: isLoadingAttributes } = useInitialPersonAttributes(patientUuid);
45
47
  const { data: identifiers, isLoading: isLoadingIdentifiers } = useInitialPatientIdentifiers(patientUuid);
46
48
  const { data: relationships, isLoading: isLoadingRelationships } = useInitialPatientRelationships(patientUuid);
@@ -62,8 +64,11 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
62
64
  birthdateEstimated: false,
63
65
  telephoneNumber: '',
64
66
  isDead: false,
65
- deathDate: '',
67
+ deathDate: undefined,
68
+ deathTime: undefined,
69
+ deathTimeFormat: 'AM',
66
70
  deathCause: '',
71
+ nonCodedCauseOfDeath: '',
67
72
  relationships: [],
68
73
  identifiers: {},
69
74
  address: {},
@@ -104,6 +109,24 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
104
109
  })();
105
110
  }, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
106
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]);
107
130
  // Setting authentication token
108
131
 
109
132
  useEffect(() => {
@@ -304,6 +327,32 @@ function useInitialPersonAttributes(personUuid: string) {
304
327
  return result;
305
328
  }
306
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
+
307
356
  function getPatientAttributeUuidMapForPatient(attributes: Array<PersonAttributeResponse>) {
308
357
  const attributeUuidMap = {};
309
358
  attributes.forEach((attribute) => {
@@ -359,9 +408,15 @@ function useConcepts() {
359
408
  },
360
409
  ];
361
410
 
362
- const martialStatus: Array<ConceptAnswers> = config.fieldDefinitions
363
- .find((fieldDefinition) => fieldDefinition.id === 'maritalStatus')
364
- .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
+ })) ?? [];
365
420
 
366
421
  return { martialStatus, education, occupation, educationLoad };
367
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) => {
@@ -1,21 +1,21 @@
1
- import React, { useState, useEffect, useContext, useMemo, useRef } from 'react';
1
+ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { Button, Link, InlineLoading } from '@carbon/react';
4
4
  import { XAxis, ShareKnowledge } from '@carbon/react/icons';
5
5
  import { useLocation, useParams } from 'react-router-dom';
6
6
  import { useTranslation } from 'react-i18next';
7
- import { Formik, Form, type FormikHelpers } from 'formik';
7
+ import { Form, Formik, type FormikHelpers } from 'formik';
8
8
  import {
9
9
  createErrorHandler,
10
+ interpolateUrl,
10
11
  showSnackbar,
11
12
  useConfig,
12
- interpolateUrl,
13
13
  usePatient,
14
14
  usePatientPhoto,
15
15
  useFeatureFlag,
16
16
  } from '@openmrs/esm-framework';
17
17
  import { getValidationSchema } from './validation/patient-registration-validation';
18
- import { type FormValues, type CapturePhotoProps } from './patient-registration.types';
18
+ import { type CapturePhotoProps, type FormValues } from './patient-registration.types';
19
19
  import { PatientRegistrationContext } from './patient-registration-context';
20
20
  import { type SavePatientForm, SavePatientTransactionManager } from './form-manager';
21
21
  import { DummyDataInput } from './input/dummy-data/dummy-data-input.component';
@@ -161,18 +161,24 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
161
161
  }
162
162
  };
163
163
 
164
+ const getDescription = (errors) => {
165
+ return (
166
+ <ul style={{ listStyle: 'inside' }}>
167
+ {Object.keys(errors).map((error, index) => {
168
+ return <li key={index}>{t(`${error}LabelText`, error)}</li>;
169
+ })}
170
+ </ul>
171
+ );
172
+ };
164
173
  const enableRegistryButton = healthInformationExchangeFlag ? false : !enableClientRegistry;
165
174
 
166
175
  const displayErrors = (errors) => {
167
176
  if (errors && typeof errors === 'object' && !!Object.keys(errors).length) {
168
- Object.keys(errors).forEach((error) => {
169
- showSnackbar({
170
- subtitle: t(`${error}LabelText`, error),
171
- title: t('incompleteForm', 'The following field has errors:'),
172
- kind: 'warning',
173
- isLowContrast: true,
174
- timeoutInMs: 5000,
175
- });
177
+ showSnackbar({
178
+ isLowContrast: true,
179
+ kind: 'warning',
180
+ title: t('fieldsWithErrors', 'The following fields have errors:'),
181
+ subtitle: <>{getDescription(errors)}</>,
176
182
  });
177
183
  }
178
184
  };
@@ -185,7 +191,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
185
191
  onSubmit={onFormSubmit}>
186
192
  {(props) => (
187
193
  <Form className={styles.form}>
188
- <BeforeSavePrompt when={props.dirty} redirect={target} />
194
+ <BeforeSavePrompt when={Object.keys(props.touched).length > 0} redirect={target} />
189
195
  <div className={styles.formContainer}>
190
196
  <div>
191
197
  <div className={styles.stickyColumn}>
@@ -250,6 +256,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
250
256
  values: props.values,
251
257
  inEditMode,
252
258
  setFieldValue: props.setFieldValue,
259
+ setFieldTouched: props.setFieldTouched,
253
260
  setCapturePhotoProps,
254
261
  currentPhoto: photo?.imageSrc,
255
262
  isOffline,
@@ -1,5 +1,6 @@
1
1
  import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
2
  import { type Patient, type Relationship, type PatientIdentifier, type Encounter } from './patient-registration.types';
3
+ import dayjs from 'dayjs';
3
4
 
4
5
  export const uuidIdentifier = '05a29f94-c0ed-11e2-94be-8c13b969e334';
5
6
  export const uuidTelephoneNumber = '14d4f066-15f5-102d-96e4-000c29c2a5d7';
@@ -188,3 +189,10 @@ export async function deletePatientIdentifier(patientUuid: string, patientIdenti
188
189
  signal: abortController.signal,
189
190
  });
190
191
  }
192
+
193
+ export function getDatetime(date: Date | string, time: string, timeFormat: 'AM' | 'PM') {
194
+ const datetime = new Date(date);
195
+ const [hours, minutes] = time.split(':').map(Number);
196
+ const fullHours = timeFormat === 'PM' ? (hours % 12) + 12 : hours % 12;
197
+ return dayjs(datetime).hour(fullHours).minute(minutes).second(0).millisecond(0).toDate();
198
+ }
@@ -14,7 +14,7 @@ import {
14
14
  import { mockedAddressTemplate } from '__mocks__';
15
15
  import { mockPatient } from 'tools';
16
16
  import { saveEncounter, savePatient } from './patient-registration.resource';
17
- import { type RegistrationConfig, esmPatientRegistrationSchema } from '../config-schema';
17
+ import { esmPatientRegistrationSchema, type RegistrationConfig } from '../config-schema';
18
18
  import type { AddressTemplate, Encounter } from './patient-registration.types';
19
19
  import { ResourcesContext } from '../offline.resources';
20
20
  import { FormManager } from './form-manager';
@@ -167,6 +167,9 @@ let mockOpenmrsConfig: RegistrationConfig = {
167
167
  searchAddressByLevel: true,
168
168
  },
169
169
  },
170
+ causeOfDeath: {
171
+ conceptUuid: 'cause-of-death-concept-uuid',
172
+ },
170
173
  },
171
174
  links: {
172
175
  submitButton: '#',
@@ -407,7 +410,7 @@ describe('Updating an existing patient record', () => {
407
410
  const givenNameInput: HTMLInputElement = screen.getByLabelText(/First Name/);
408
411
  const familyNameInput: HTMLInputElement = screen.getByLabelText(/Family Name/);
409
412
  const middleNameInput: HTMLInputElement = screen.getByLabelText(/Middle Name/);
410
- const dateOfBirthInput: HTMLInputElement = screen.getByLabelText('Date of Birth');
413
+ const dateOfBirthInput: HTMLInputElement = screen.getByLabelText(/Date of Birth/i);
411
414
  const genderInput: HTMLInputElement = screen.getByLabelText(/Male/);
412
415
 
413
416
  // assert initial values
@@ -443,7 +446,10 @@ describe('Updating an existing patient record', () => {
443
446
  birthdate: new Date('1972-04-04T00:00:00.000Z'),
444
447
  birthdateEstimated: false,
445
448
  deathCause: '',
446
- deathDate: '',
449
+ nonCodedCauseOfDeath: '',
450
+ deathDate: undefined,
451
+ deathTime: undefined,
452
+ deathTimeFormat: 'AM',
447
453
  familyName: 'Smith',
448
454
  gender: expect.stringMatching(/male/i),
449
455
  givenName: 'Eric',
@@ -167,7 +167,9 @@ export interface FormValues {
167
167
  birthdate: Date | string;
168
168
  birthdateEstimated: boolean;
169
169
  deathCause: string;
170
- deathDate: string;
170
+ deathDate: string | Date;
171
+ deathTime: string;
172
+ deathTimeFormat: 'AM' | 'PM';
171
173
  familyName: string;
172
174
  gender: string;
173
175
  givenName: string;
@@ -177,6 +179,7 @@ export interface FormValues {
177
179
  isDead: boolean;
178
180
  middleName: string;
179
181
  monthsEstimated: number;
182
+ nonCodedCauseOfDeath: string;
180
183
  obs?: {
181
184
  [conceptUuid: string]: string;
182
185
  };
@@ -1,30 +1,35 @@
1
- import React from 'react';
2
- import classNames from 'classnames';
1
+ import React, { useContext } from 'react';
3
2
  import { useTranslation } from 'react-i18next';
4
- import { Input } from '../../input/basic-input/input/input.component';
5
- import { SelectInput } from '../../input/basic-input/select/select-input.component';
3
+ import { Checkbox, Layer } from '@carbon/react';
4
+ import { useField } from 'formik';
5
+ import { Field } from '../../field/field.component';
6
6
  import { PatientRegistrationContext } from '../../patient-registration-context';
7
7
  import styles from './../section.scss';
8
8
 
9
- export const DeathInfoSection = () => {
10
- const { values } = React.useContext(PatientRegistrationContext);
9
+ export interface DeathInfoSectionProps {
10
+ fields: Array<string>;
11
+ }
12
+
13
+ export const DeathInfoSection: React.FC<DeathInfoSectionProps> = ({ fields }) => {
11
14
  const { t } = useTranslation();
15
+ const { values, setFieldValue } = useContext(PatientRegistrationContext);
16
+ const [deathDate, deathDateMeta] = useField('deathDate');
17
+ const today = new Date();
12
18
 
13
19
  return (
14
20
  <section className={styles.formSection} aria-label="Death Info Section">
15
- <h5 className={classNames('omrs-type-title-5', styles.formSectionTitle)}>Death Info</h5>
16
21
  <section className={styles.fieldGroup}>
17
- <Input labelText={t('isDeadInputLabel', 'Is Dead')} name="isDead" id="isDead" />
18
- {values.isDead && (
19
- <>
20
- <Input labelText={t('deathDateInputLabel', 'Date of Death')} name="deathDate" id="deathDate" />
21
- <SelectInput
22
- options={[t('unknown', 'Unknown'), t('stroke', 'Stroke')]}
23
- label={t('causeOfDeathInputLabel', 'Cause of Death')}
24
- name="deathCause"
22
+ <Layer>
23
+ <div className={styles.isDeadFieldContainer}>
24
+ <Checkbox
25
+ checked={values.isDead}
26
+ id="isDead"
27
+ labelText={t('isDeadInputLabel', 'Is dead')}
28
+ onChange={(event, { checked, id }) => setFieldValue(id, checked)}
25
29
  />
26
- </>
27
- )}
30
+ </div>
31
+ </Layer>
32
+ {values.isDead ? fields.map((field) => <Field key={`death-info-${field}`} name={field} />) : null}
28
33
  </section>
29
34
  </section>
30
35
  );