@kenyaemr/esm-patient-registration-app 8.0.1-pre.95 → 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 (134) 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 +2 -2
  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 +4 -1
  52. package/src/{patient-verification → client-registry/patient-verification}/patient-verification.scss +17 -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/obs/obs-field.component.tsx +1 -1
  68. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +1 -0
  69. package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +76 -27
  70. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +105 -0
  71. package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +48 -0
  72. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +12 -1
  73. package/src/patient-registration/field/person-attributes/useUpdateIdentifierRequirement.tsx +83 -0
  74. package/src/patient-registration/form-manager.test.ts +21 -0
  75. package/src/patient-registration/form-manager.ts +40 -20
  76. package/src/patient-registration/input/basic-input/input/input.component.tsx +5 -1
  77. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +18 -10
  78. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +166 -67
  79. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +3 -0
  80. package/src/patient-registration/input/input.scss +5 -0
  81. package/src/patient-registration/patient-registration-context.ts +4 -3
  82. package/src/patient-registration/patient-registration-hooks.ts +67 -9
  83. package/src/patient-registration/patient-registration-utils.ts +3 -7
  84. package/src/patient-registration/patient-registration.component.tsx +44 -30
  85. package/src/patient-registration/patient-registration.resource.ts +8 -0
  86. package/src/patient-registration/patient-registration.test.tsx +9 -3
  87. package/src/patient-registration/patient-registration.types.ts +4 -1
  88. package/src/patient-registration/section/death-info/death-info-section.component.tsx +22 -17
  89. package/src/patient-registration/section/death-info/death-info-section.test.tsx +4 -14
  90. package/src/patient-registration/section/section.component.tsx +1 -1
  91. package/src/patient-registration/section/section.scss +5 -0
  92. package/src/patient-registration/validation/{patient-registration-validation.test.tsx → patient-registration-validation.test.ts} +26 -4
  93. package/src/patient-registration/validation/patient-registration-validation.ts +126 -0
  94. package/src/routes.json +14 -17
  95. package/src/widgets/cancel-patient-edit.modal.tsx +33 -0
  96. package/src/widgets/cancel-patient-edit.test.tsx +2 -3
  97. package/src/widgets/delete-identifier-confirmation.modal.tsx +22 -15
  98. package/src/widgets/delete-identifier-confirmation.test.tsx +2 -1
  99. package/translations/am.json +36 -25
  100. package/translations/ar.json +37 -26
  101. package/translations/en.json +51 -20
  102. package/translations/es.json +38 -26
  103. package/translations/fr.json +47 -35
  104. package/translations/he.json +37 -30
  105. package/translations/km.json +37 -30
  106. package/translations/zh.json +37 -20
  107. package/translations/zh_CN.json +37 -20
  108. package/dist/152.js +0 -1
  109. package/dist/152.js.map +0 -1
  110. package/dist/255.js +0 -2
  111. package/dist/255.js.map +0 -1
  112. package/dist/303.js +0 -1
  113. package/dist/303.js.map +0 -1
  114. package/dist/330.js +0 -1
  115. package/dist/330.js.map +0 -1
  116. package/dist/564.js +0 -1
  117. package/dist/564.js.map +0 -1
  118. package/dist/623.js +0 -1
  119. package/dist/623.js.map +0 -1
  120. package/dist/729.js +0 -1
  121. package/dist/729.js.map +0 -1
  122. package/dist/735.js +0 -1
  123. package/dist/735.js.map +0 -1
  124. package/dist/831.js +0 -2
  125. package/dist/831.js.LICENSE.txt +0 -14
  126. package/dist/831.js.map +0 -1
  127. package/src/patient-registration/validation/patient-registration-validation.tsx +0 -60
  128. package/src/widgets/cancel-patient-edit.component.tsx +0 -37
  129. package/src/widgets/delete-identifier-confirmation.scss +0 -34
  130. /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
  131. /package/src/{patient-verification → client-registry/patient-verification}/assets/counties.json +0 -0
  132. /package/src/{patient-verification → client-registry/patient-verification}/assets/verification-assets.ts +0 -0
  133. /package/src/{patient-verification → client-registry/patient-verification}/verification-modal/confirm-prompt.component.tsx +0 -0
  134. /package/src/{patient-verification → client-registry/patient-verification}/verification-types.ts +0 -0
@@ -18,6 +18,7 @@ export interface FieldDefinition {
18
18
  required: boolean;
19
19
  matches?: string;
20
20
  };
21
+ locationTag?: string;
21
22
  answerConceptSetUuid?: string;
22
23
  customConceptAnswers?: Array<CustomConceptAnswer>;
23
24
  showWhenExpression?: {
@@ -42,6 +43,10 @@ export interface RegistrationConfig {
42
43
  sectionDefinitions: Array<SectionDefinition>;
43
44
  fieldDefinitions: Array<FieldDefinition>;
44
45
  fieldConfigurations: {
46
+ causeOfDeath: {
47
+ conceptUuid: string;
48
+ required?: boolean;
49
+ };
45
50
  name: {
46
51
  displayMiddleName: boolean;
47
52
  allowUnidentifiedPatients: boolean;
@@ -83,6 +88,12 @@ export interface RegistrationConfig {
83
88
  encounterProviderRoleUuid: string;
84
89
  registrationFormUuid: string | null;
85
90
  };
91
+ freeTextFieldConceptUuid: string;
92
+ hieClientRegistry: {
93
+ identifierTypes: Array<{ identifierType: string; identifierValue: string }>;
94
+ baseUrl: string;
95
+ encodedCredentials: string;
96
+ };
86
97
  }
87
98
 
88
99
  export const builtInSections: Array<SectionDefinition> = [
@@ -92,12 +103,21 @@ export const builtInSections: Array<SectionDefinition> = [
92
103
  fields: ['name', 'gender', 'dob', 'id'],
93
104
  },
94
105
  { id: 'contact', name: 'Contact Details', fields: ['address', 'phone'] },
95
- { id: 'death', name: 'Death Info', fields: [] },
106
+ { id: 'death', name: 'Death Info', fields: ['dateAndTimeOfDeath', 'causeOfDeath'] },
96
107
  { id: 'relationships', name: 'Relationships', fields: [] },
97
108
  ];
98
109
 
99
110
  // These fields are handled specially in field.component.tsx
100
- export const builtInFields = ['name', 'gender', 'dob', 'id', 'address', 'phone'] as const;
111
+ export const builtInFields = [
112
+ 'name',
113
+ 'gender',
114
+ 'dob',
115
+ 'id',
116
+ 'address',
117
+ 'phone',
118
+ 'causeOfDeath',
119
+ 'dateAndTimeOfDeath',
120
+ ] as const;
101
121
 
102
122
  export const esmPatientRegistrationSchema = {
103
123
  sections: {
@@ -174,6 +194,12 @@ export const esmPatientRegistrationSchema = {
174
194
  _description: 'Optional RegEx for testing the validity of the input.',
175
195
  },
176
196
  },
197
+ locationTag: {
198
+ _type: Type.String,
199
+ _default: null,
200
+ _description:
201
+ 'Only for fields with "person attribute" type `org.openmrs.Location`. This filters the list of location options in the dropdown based on their location tag. By default, all locations are shown.',
202
+ },
177
203
  answerConceptSetUuid: {
178
204
  _type: Type.ConceptUuid,
179
205
  _default: null,
@@ -204,6 +230,14 @@ export const esmPatientRegistrationSchema = {
204
230
  'Definitions for custom fields that can be used in sectionDefinitions. Can also be used to override built-in fields.',
205
231
  },
206
232
  fieldConfigurations: {
233
+ causeOfDeath: {
234
+ conceptUuid: {
235
+ _type: Type.ConceptUuid,
236
+ _description: 'The concept UUID to get cause of death answers',
237
+ _default: '9272a14b-7260-4353-9e5b-5787b5dead9d',
238
+ },
239
+ required: { _type: Type.Boolean, _default: false },
240
+ },
207
241
  name: {
208
242
  displayMiddleName: { _type: Type.Boolean, _default: true },
209
243
  allowUnidentifiedPatients: {
@@ -364,6 +398,42 @@ export const esmPatientRegistrationSchema = {
364
398
  'The form UUID to associate with the registration encounter. By default no form will be associated.',
365
399
  },
366
400
  },
401
+ freeTextFieldConceptUuid: {
402
+ _default: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
403
+ _type: Type.ConceptUuid,
404
+ },
405
+ hieClientRegistry: {
406
+ identifierTypes: {
407
+ _type: Type.Array,
408
+ _elements: {
409
+ identifierType: {
410
+ _type: Type.String,
411
+ _description: 'The label of the identifier type',
412
+ },
413
+ identifierValue: {
414
+ _type: Type.String,
415
+ _description: 'The value of the identifier type',
416
+ },
417
+ },
418
+ _default: [
419
+ { identifierType: 'National ID', identifierValue: 'national-id' },
420
+ { identifierType: 'Passport Number', identifierValue: 'passport-number' },
421
+ { identifierType: 'Birth Certificate Number', identifierValue: 'birth-certificate-number' },
422
+ { identifierType: 'Alien ID Number', identifierValue: 'alien-id-number' },
423
+ { identifierType: 'Refugee ID Number', identifierValue: 'refugee-number' },
424
+ ],
425
+ },
426
+ baseUrl: {
427
+ _type: Type.String,
428
+ _default: 'https://hie.paperless.co.ke/v4/custom/',
429
+ _description: 'The base URL for the HIE API',
430
+ },
431
+ encodedCredentials: {
432
+ _type: Type.String,
433
+ _default: 'a2VueWFfZW1yOkFsZG5iJmtmayZKc2whMjM0',
434
+ _description: 'The base64 encoded credentials for the HIE API',
435
+ },
436
+ },
367
437
  _validators: [
368
438
  validator(
369
439
  (config: RegistrationConfig) =>
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import rootComponent from './root.component';
6
6
  import addPatientLinkComponent from './add-patient-link';
7
7
  import editPatientDetailsButtonComponent from './widgets/edit-patient-details-button.component';
8
8
  import { PatientPhotoExtension } from './patient-photo.extension';
9
+ import HIEConfirmationModal from './client-registry/hie-client-registry/modal/confirm-hie.modal';
9
10
 
10
11
  export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
11
12
 
@@ -50,10 +51,7 @@ export const editPatient = getSyncLifecycle(rootComponent, {
50
51
 
51
52
  export const addPatientLink = getSyncLifecycle(addPatientLinkComponent, options);
52
53
 
53
- export const cancelPatientEditModal = getAsyncLifecycle(
54
- () => import('./widgets/cancel-patient-edit.component'),
55
- options,
56
- );
54
+ export const cancelPatientEditModal = getAsyncLifecycle(() => import('./widgets/cancel-patient-edit.modal'), options);
57
55
 
58
56
  export const patientPhotoExtension = getSyncLifecycle(PatientPhotoExtension, options);
59
57
 
@@ -68,11 +66,13 @@ export const deleteIdentifierConfirmationModal = getAsyncLifecycle(
68
66
  );
69
67
 
70
68
  export const confirmClientRegistryModal = getAsyncLifecycle(
71
- () => import('./patient-verification/verification-modal/confirm-prompt.component'),
69
+ () => import('./client-registry/patient-verification/verification-modal/confirm-prompt.component'),
72
70
  options,
73
71
  );
74
72
 
75
73
  export const emptyClientRegistryModal = getAsyncLifecycle(
76
- () => import('./patient-verification/verification-modal/empty-prompt.component'),
74
+ () => import('./client-registry/patient-verification/verification-modal/empty-prompt.component'),
77
75
  options,
78
76
  );
77
+
78
+ export const hieConfirmationModal = getSyncLifecycle(HIEConfirmationModal, options);
@@ -0,0 +1,98 @@
1
+ import React, { useMemo } from 'react';
2
+ import classNames from 'classnames';
3
+ import { Field, useField } from 'formik';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { InlineNotification, Layer, Select, SelectItem, SelectSkeleton, TextInput } from '@carbon/react';
6
+ import { useConfig } from '@openmrs/esm-framework';
7
+ import { type RegistrationConfig } from '../../../config-schema';
8
+ import { useConceptAnswers } from '../field.resource';
9
+ import styles from '../field.scss';
10
+
11
+ export const CauseOfDeathField: React.FC = () => {
12
+ const { t } = useTranslation();
13
+ const { fieldConfigurations, freeTextFieldConceptUuid } = useConfig<RegistrationConfig>();
14
+ const [deathCause, deathCauseMeta] = useField('deathCause');
15
+
16
+ const conceptUuid = fieldConfigurations?.causeOfDeath?.conceptUuid;
17
+ const required = fieldConfigurations?.causeOfDeath?.required;
18
+
19
+ const {
20
+ data: conceptAnswers,
21
+ isLoading: isLoadingConceptAnswers,
22
+ error: errorLoadingConceptAnswers,
23
+ } = useConceptAnswers(conceptUuid);
24
+
25
+ const answers = useMemo(() => {
26
+ if (!isLoadingConceptAnswers && conceptAnswers) {
27
+ return conceptAnswers.map((answer) => ({ ...answer, label: answer.display }));
28
+ }
29
+ return [];
30
+ }, [conceptAnswers, isLoadingConceptAnswers]);
31
+
32
+ if (isLoadingConceptAnswers) {
33
+ return (
34
+ <div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
35
+ <h4 className={styles.productiveHeading02Light}>{t('causeOfDeathInputLabel', 'Cause of death')}</h4>
36
+ <SelectSkeleton />
37
+ </div>
38
+ );
39
+ }
40
+
41
+ return (
42
+ <div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
43
+ <h4 className={styles.productiveHeading02Light}>{t('causeOfDeathInputLabel', 'Cause of death')}</h4>
44
+ {errorLoadingConceptAnswers || !conceptUuid ? (
45
+ <InlineNotification
46
+ hideCloseButton
47
+ kind="error"
48
+ title={t('errorFetchingCodedCausesOfDeath', 'Error fetching coded causes of death')}
49
+ subtitle={t('refreshOrContactAdmin', 'Try refreshing the page or contact your system administrator')}
50
+ />
51
+ ) : (
52
+ <>
53
+ <Field name="deathCause">
54
+ {({ field, form: { touched, errors }, meta }) => {
55
+ return (
56
+ <Layer>
57
+ <Select
58
+ {...field}
59
+ id="deathCause"
60
+ invalid={errors.deathCause && touched.deathCause}
61
+ invalidText={errors.deathCause?.message}
62
+ labelText={t('causeOfDeathInputLabel', 'Cause of Death')}
63
+ name="deathCause"
64
+ required={required}>
65
+ <SelectItem id="empty-default-option" value={null} text={t('selectAnOption', 'Select an option')} />
66
+ {answers.map((answer) => (
67
+ <SelectItem id={answer.uuid} key={answer.uuid} text={answer.label} value={answer.uuid} />
68
+ ))}
69
+ </Select>
70
+ </Layer>
71
+ );
72
+ }}
73
+ </Field>
74
+ {deathCause.value === freeTextFieldConceptUuid && (
75
+ <div className={styles.nonCodedCauseOfDeath}>
76
+ <Field name="nonCodedCauseOfDeath">
77
+ {({ field, form: { touched, errors }, meta }) => {
78
+ return (
79
+ <Layer>
80
+ <TextInput
81
+ {...field}
82
+ id="nonCodedCauseOfDeath"
83
+ invalid={errors?.nonCodedCauseOfDeath && touched.nonCodedCauseOfDeath}
84
+ invalidText={errors?.nonCodedCauseOfDeath?.message}
85
+ labelText={t('nonCodedCauseOfDeath', 'Non-coded cause of death')}
86
+ placeholder={t('enterNonCodedCauseOfDeath', 'Enter non-coded cause of death')}
87
+ />
88
+ </Layer>
89
+ );
90
+ }}
91
+ </Field>
92
+ </div>
93
+ )}
94
+ </>
95
+ )}
96
+ </div>
97
+ );
98
+ };
@@ -0,0 +1,84 @@
1
+ import React, { useCallback, useContext } from 'react';
2
+ import classNames from 'classnames';
3
+ import dayjs from 'dayjs';
4
+ import { Layer, SelectItem, TimePicker, TimePickerSelect } from '@carbon/react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { useField } from 'formik';
7
+ import { OpenmrsDatePicker } from '@openmrs/esm-framework';
8
+ import { PatientRegistrationContext } from '../../patient-registration-context';
9
+ import type { FormValues } from '../../patient-registration.types';
10
+ import styles from '../field.scss';
11
+
12
+ export const DateAndTimeOfDeathField: React.FC = () => {
13
+ const { t } = useTranslation();
14
+
15
+ return (
16
+ <div className={classNames(styles.dodField, styles.halfWidthInDesktopView)}>
17
+ <h4 className={styles.productiveHeading02Light}>{t('deathDateInputLabel', 'Date of Death')}</h4>
18
+ <span>
19
+ <DeathDateField />
20
+ <DeathTimeField />
21
+ </span>
22
+ </div>
23
+ );
24
+ };
25
+
26
+ function DeathDateField() {
27
+ const { values, setFieldValue } = useContext(PatientRegistrationContext);
28
+ const [deathDate, deathDateMeta] = useField<keyof FormValues>('deathDate');
29
+ const { t } = useTranslation();
30
+ const today = dayjs().hour(23).minute(59).second(59).toDate();
31
+ const onDateChange = useCallback(
32
+ (selectedDate: Date) => {
33
+ setFieldValue(
34
+ 'deathDate',
35
+ selectedDate ? dayjs(selectedDate).hour(0).minute(0).second(0).millisecond(0).toDate() : undefined,
36
+ );
37
+ },
38
+ [deathDate],
39
+ );
40
+
41
+ return (
42
+ <Layer>
43
+ <OpenmrsDatePicker
44
+ {...deathDate}
45
+ id="deathDate"
46
+ invalidText={t(deathDateMeta.error)}
47
+ invalid={!!(deathDateMeta.touched && deathDateMeta.error)}
48
+ isRequired={values.isDead}
49
+ labelText={t('deathDateInputLabel', 'Date of death')}
50
+ maxDate={today}
51
+ onChange={onDateChange}
52
+ />
53
+ </Layer>
54
+ );
55
+ }
56
+
57
+ function DeathTimeField() {
58
+ const { t } = useTranslation();
59
+ const [deathTimeField, deathTimeMeta] = useField<keyof FormValues>('deathTime');
60
+ const [deathTimeFormatField, deathTimeFormatMeta] = useField<keyof FormValues>('deathTimeFormat');
61
+
62
+ return (
63
+ <Layer>
64
+ <TimePicker
65
+ {...deathTimeField}
66
+ id="time-picker"
67
+ labelText={t('timeOfDeathInputLabel', 'Time of death (hh:mm)')}
68
+ className={styles.timeOfDeathField}
69
+ pattern="^(1[0-2]|0?[1-9]):([0-5]?[0-9])$"
70
+ invalid={!!(deathTimeMeta.touched && deathTimeMeta.error)}
71
+ invalidText={t(deathTimeMeta.error)}>
72
+ <TimePickerSelect
73
+ {...deathTimeFormatField}
74
+ id="time-format-picker"
75
+ aria-label={t('timeFormat', 'Time Format')}
76
+ invalid={!!deathTimeFormatMeta.touched && deathTimeFormatMeta.error}
77
+ invalidText={t(deathTimeFormatMeta.error)}>
78
+ <SelectItem value="AM" text="AM" />
79
+ <SelectItem value="PM" text="PM" />
80
+ </TimePickerSelect>
81
+ </TimePicker>
82
+ </Layer>
83
+ );
84
+ }
@@ -30,7 +30,7 @@ export const DobField: React.FC = () => {
30
30
  const [birthdate, birthdateMeta] = useField('birthdate');
31
31
  const [yearsEstimated, yearsEstimateMeta] = useField('yearsEstimated');
32
32
  const [monthsEstimated, monthsEstimateMeta] = useField('monthsEstimated');
33
- const { setFieldValue } = useContext(PatientRegistrationContext);
33
+ const { setFieldValue, setFieldTouched } = useContext(PatientRegistrationContext);
34
34
  const today = new Date();
35
35
 
36
36
  const onToggle = useCallback(
@@ -39,6 +39,7 @@ export const DobField: React.FC = () => {
39
39
  setFieldValue('birthdate', '');
40
40
  setFieldValue('yearsEstimated', 0);
41
41
  setFieldValue('monthsEstimated', '');
42
+ setFieldTouched('birthdateEstimated', true, false);
42
43
  },
43
44
  [setFieldValue],
44
45
  );
@@ -46,8 +47,9 @@ export const DobField: React.FC = () => {
46
47
  const onDateChange = useCallback(
47
48
  (birthdate: Date) => {
48
49
  setFieldValue('birthdate', birthdate);
50
+ setFieldTouched('birthdate', true, false);
49
51
  },
50
- [setFieldValue],
52
+ [setFieldValue, setFieldTouched],
51
53
  );
52
54
 
53
55
  const onEstimatedYearsChange = useCallback(
@@ -80,7 +82,10 @@ export const DobField: React.FC = () => {
80
82
  setFieldValue('yearsEstimated', years);
81
83
  setFieldValue('monthsEstimated', months > 0 ? months : '');
82
84
  setFieldValue('birthdate', calcBirthdate(years, months, dateOfBirth));
83
- }, [setFieldValue, monthsEstimateMeta, yearsEstimateMeta, dateOfBirth]);
85
+ setFieldTouched('yearsEstimated', true, false);
86
+ setFieldTouched('monthsEstimated', true, false);
87
+ setFieldTouched('birthdate', true, false);
88
+ }, [setFieldValue, setFieldTouched, monthsEstimateMeta, yearsEstimateMeta, dateOfBirth]);
84
89
 
85
90
  return (
86
91
  <div className={styles.halfWidthInDesktopView}>
@@ -88,7 +93,7 @@ export const DobField: React.FC = () => {
88
93
  {(allowEstimatedBirthDate || dobUnknown) && (
89
94
  <div className={styles.dobField}>
90
95
  <div className={styles.dobContentSwitcherLabel}>
91
- <span className={styles.label01}>{t('dobToggleLabelText', 'Date of Birth Known?')}</span>
96
+ <span className={styles.label01}>{t('dobToggleLabelText', 'Date of birth known?')}</span>
92
97
  </div>
93
98
  <ContentSwitcher onChange={onToggle} selectedIndex={dobUnknown ? 1 : 0}>
94
99
  <Switch name="known" text={t('yes', 'Yes')} />
@@ -103,8 +108,9 @@ export const DobField: React.FC = () => {
103
108
  id="birthdate"
104
109
  {...birthdate}
105
110
  onChange={onDateChange}
111
+ onBlur={() => setFieldTouched('birthdate', true, false)}
106
112
  maxDate={today}
107
- labelText={t('dateOfBirthLabelText', 'Date of Birth')}
113
+ labelText={t('dateOfBirthLabelText', 'Date of birth')}
108
114
  isInvalid={!!(birthdateMeta.touched && birthdateMeta.error)}
109
115
  invalidText={t(birthdateMeta.error)}
110
116
  value={birthdate.value}
@@ -125,7 +131,11 @@ export const DobField: React.FC = () => {
125
131
  min={0}
126
132
  required
127
133
  {...yearsEstimated}
128
- onBlur={updateBirthdate}
134
+ onBlur={(e) => {
135
+ yearsEstimated.onBlur(e);
136
+ setFieldTouched('yearsEstimated', true, false);
137
+ updateBirthdate();
138
+ }}
129
139
  />
130
140
  </div>
131
141
  <div className={styles.dobField}>
@@ -141,7 +151,11 @@ export const DobField: React.FC = () => {
141
151
  min={0}
142
152
  {...monthsEstimated}
143
153
  required={!yearsEstimateMeta.value}
144
- onBlur={updateBirthdate}
154
+ onBlur={(e) => {
155
+ monthsEstimated.onBlur(e);
156
+ setFieldTouched('monthsEstimated', true, false);
157
+ updateBirthdate();
158
+ }}
145
159
  />
146
160
  </div>
147
161
  </div>
@@ -1,12 +1,14 @@
1
1
  import React from 'react';
2
- import { NameField } from './name/name-field.component';
3
- import { GenderField } from './gender/gender-field.component';
4
- import { Identifiers } from './id/id-field.component';
5
- import { DobField } from './dob/dob.component';
6
2
  import { reportError, useConfig } from '@openmrs/esm-framework';
7
3
  import { builtInFields, type RegistrationConfig } from '../../config-schema';
8
- import { CustomField } from './custom-field.component';
9
4
  import { AddressComponent } from './address/address-field.component';
5
+ import { CauseOfDeathField } from './cause-of-death/cause-of-death.component';
6
+ import { CustomField } from './custom-field.component';
7
+ import { DateAndTimeOfDeathField } from './date-and-time-of-death/date-and-time-of-death.component';
8
+ import { DobField } from './dob/dob.component';
9
+ import { GenderField } from './gender/gender-field.component';
10
+ import { Identifiers } from './id/id-field.component';
11
+ import { NameField } from './name/name-field.component';
10
12
  import { PhoneField } from './phone/phone-field.component';
11
13
 
12
14
  export interface FieldProps {
@@ -35,6 +37,10 @@ export function Field({ name }: FieldProps) {
35
37
  return <GenderField />;
36
38
  case 'dob':
37
39
  return <DobField />;
40
+ case 'dateAndTimeOfDeath':
41
+ return <DateAndTimeOfDeathField />;
42
+ case 'causeOfDeath':
43
+ return <CauseOfDeathField />;
38
44
  case 'address':
39
45
  return <AddressComponent />;
40
46
  case 'id':
@@ -1,6 +1,7 @@
1
- import { type FetchResponse, openmrsFetch, showSnackbar, restBaseUrl } from '@openmrs/esm-framework';
1
+ import { type FetchResponse, openmrsFetch, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
2
2
  import useSWRImmutable from 'swr/immutable';
3
3
  import { type ConceptAnswers, type ConceptResponse } from '../patient-registration.types';
4
+ import { useMemo } from 'react';
4
5
 
5
6
  export function useConcept(conceptUuid: string): { data: ConceptResponse; isLoading: boolean } {
6
7
  const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== '';
@@ -15,10 +16,15 @@ export function useConcept(conceptUuid: string): { data: ConceptResponse; isLoad
15
16
  kind: 'error',
16
17
  });
17
18
  }
18
- return { data: data?.data, isLoading };
19
+ const results = useMemo(() => ({ data: data?.data, isLoading }), [data, isLoading]);
20
+ return results;
19
21
  }
20
22
 
21
- export function useConceptAnswers(conceptUuid: string): { data: Array<ConceptAnswers>; isLoading: boolean } {
23
+ export function useConceptAnswers(conceptUuid: string): {
24
+ data: Array<ConceptAnswers>;
25
+ isLoading: boolean;
26
+ error: Error;
27
+ } {
22
28
  const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== '';
23
29
  const { data, error, isLoading } = useSWRImmutable<FetchResponse<ConceptResponse>, Error>(
24
30
  shouldFetch ? `${restBaseUrl}/concept/${conceptUuid}` : null,
@@ -31,5 +37,6 @@ export function useConceptAnswers(conceptUuid: string): { data: Array<ConceptAns
31
37
  kind: 'error',
32
38
  });
33
39
  }
34
- return { data: data?.data?.answers, isLoading };
40
+ const results = useMemo(() => ({ data: data?.data?.answers, isLoading, error }), [isLoading, error, data]);
41
+ return results;
35
42
  }
@@ -64,8 +64,30 @@
64
64
  }
65
65
 
66
66
  .sexField,
67
- .dobField {
67
+ .dobField,
68
+ .dodField {
68
69
  margin-bottom: layout.$spacing-05;
70
+
71
+ span {
72
+ display: flex;
73
+ flex-flow: row nowrap;
74
+ justify-content: space-between;
75
+ align-items: start;
76
+ }
77
+ }
78
+
79
+ .nonCodedCauseOfDeath {
80
+ margin-top: layout.$spacing-04;
81
+ }
82
+
83
+ .timeOfDeathContainer {
84
+ display: flex;
85
+ align-items: center;
86
+ }
87
+
88
+ .timeOfDeathField {
89
+ flex: none;
90
+ margin-left: layout.$spacing-02;
69
91
  }
70
92
 
71
93
  .dobContentSwitcherLabel {
@@ -77,22 +99,35 @@
77
99
  align-items: center;
78
100
  }
79
101
 
102
+ .arrowRightIcon {
103
+ fill: currentColor !important;
104
+ }
105
+
80
106
  .configureIdentifiersButton {
107
+ display: flex;
108
+ align-items: center;
81
109
  margin: 0 0 layout.$spacing-05 layout.$spacing-05;
82
110
 
83
111
  svg {
84
112
  margin-left: layout.$spacing-03;
85
113
  }
86
-
87
- .customField {
88
- margin-bottom: layout.$spacing-05;
89
- }
90
114
  }
91
115
 
92
116
  .attributeField {
93
117
  margin-bottom: layout.$spacing-05;
94
118
  }
95
119
 
120
+ .locationAttributeFieldContainer {
121
+ position: relative;
122
+
123
+ .loadingContainer {
124
+ background-color: colors.$white;
125
+ position: absolute;
126
+ right: layout.$spacing-07;
127
+ bottom: layout.$spacing-02;
128
+ }
129
+ }
130
+
96
131
  :global(.omrs-breakpoint-lt-desktop) {
97
132
  .grid {
98
133
  grid-template-columns: 1fr;
@@ -130,3 +165,7 @@
130
165
  line-height: 1.34;
131
166
  margin: layout.$spacing-02 0 0;
132
167
  }
168
+
169
+ .customField {
170
+ margin-bottom: layout.$spacing-05;
171
+ }
@@ -11,11 +11,12 @@ export const GenderField: React.FC = () => {
11
11
  const { fieldConfigurations } = useConfig<RegistrationConfig>();
12
12
  const { t } = useTranslation();
13
13
  const [field, meta] = useField('gender');
14
- const { setFieldValue } = useContext(PatientRegistrationContext);
14
+ const { setFieldValue, setFieldTouched } = useContext(PatientRegistrationContext);
15
15
  const fieldConfigs = fieldConfigurations?.gender;
16
16
 
17
17
  const setGender = (gender: string) => {
18
18
  setFieldValue('gender', gender);
19
+ setFieldTouched('gender', true, false);
19
20
  };
20
21
  /**
21
22
  * DO NOT REMOVE THIS COMMENT HERE, ADDS TRANSLATION FOR SEX OPTIONS
@@ -12,6 +12,7 @@ jest.mock('react', () => ({
12
12
  ...(jest.requireActual('react') as any),
13
13
  useContext: jest.fn(() => ({
14
14
  setFieldValue: jest.fn(),
15
+ setFieldTouched: jest.fn(),
15
16
  })),
16
17
  }));
17
18
 
@@ -25,14 +25,16 @@ export function setIdentifierSource(
25
25
  selectedSource: IdentifierSource;
26
26
  } {
27
27
  const autoGeneration = identifierSource?.autoGenerationOption?.automaticGenerationEnabled;
28
+ const manualEntryEnabled = identifierSource?.autoGenerationOption?.manualEntryEnabled;
28
29
  return {
29
30
  selectedSource: identifierSource,
30
31
  autoGeneration,
31
- identifierValue: autoGeneration
32
- ? 'auto-generated'
33
- : identifierValue !== 'auto-generated'
34
- ? identifierValue
35
- : initialValue,
32
+ identifierValue:
33
+ autoGeneration && !manualEntryEnabled
34
+ ? 'auto-generated'
35
+ : identifierValue !== 'auto-generated'
36
+ ? identifierValue
37
+ : initialValue,
36
38
  };
37
39
  }
38
40
 
@@ -126,7 +128,7 @@ export const Identifiers: React.FC = () => {
126
128
  className={styles.configureIdentifiersButton}
127
129
  onClick={() => setShowIdentifierOverlay(true)}
128
130
  size={isDesktop(layout) ? 'sm' : 'md'}>
129
- {t('configure', 'Configure')} <ArrowRight size={16} />
131
+ {t('configure', 'Configure')} <ArrowRight className={styles.arrowRightIcon} size={16} />
130
132
  </Button>
131
133
  </div>
132
134
  </UserHasAccess>