@kenyaemr/esm-patient-registration-app 8.1.1-pre.124 → 8.1.2-pre.152

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 (127) hide show
  1. package/.turbo/turbo-build.log +21 -23
  2. package/dist/108.js +1 -1
  3. package/dist/130.js +1 -1
  4. package/dist/130.js.map +1 -1
  5. package/dist/173.js +2 -0
  6. package/dist/{895.js.LICENSE.txt → 173.js.LICENSE.txt} +25 -0
  7. package/dist/173.js.map +1 -0
  8. package/dist/236.js +1 -0
  9. package/dist/240.js +1 -0
  10. package/dist/261.js +1 -0
  11. package/dist/271.js +1 -1
  12. package/dist/272.js +1 -0
  13. package/dist/319.js +1 -1
  14. package/dist/336.js +1 -0
  15. package/dist/371.js +1 -0
  16. package/dist/371.js.map +1 -0
  17. package/dist/378.js +1 -0
  18. package/dist/460.js +1 -1
  19. package/dist/501.js +1 -1
  20. package/dist/501.js.map +1 -1
  21. package/dist/539.js +1 -0
  22. package/dist/566.js +1 -0
  23. package/dist/574.js +1 -1
  24. package/dist/623.js +1 -0
  25. package/dist/623.js.map +1 -0
  26. package/dist/644.js +1 -1
  27. package/dist/652.js +1 -0
  28. package/dist/657.js +1 -0
  29. package/dist/657.js.map +1 -0
  30. package/dist/673.js +1 -0
  31. package/dist/705.js +1 -0
  32. package/dist/711.js +1 -0
  33. package/dist/727.js +1 -0
  34. package/dist/737.js +1 -0
  35. package/dist/744.js +1 -0
  36. package/dist/757.js +1 -1
  37. package/dist/759.js +1 -0
  38. package/dist/759.js.map +1 -0
  39. package/dist/76.js +1 -1
  40. package/dist/788.js +1 -1
  41. package/dist/807.js +1 -1
  42. package/dist/833.js +1 -1
  43. package/dist/899.js +1 -0
  44. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  45. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +445 -93
  46. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  47. package/dist/main.js +1 -1
  48. package/dist/main.js.LICENSE.txt +25 -0
  49. package/dist/main.js.map +1 -1
  50. package/dist/routes.json +1 -1
  51. package/package-lock.json +6400 -0
  52. package/package.json +4 -4
  53. package/src/client-registry/hie-client-registry/dependants/dependants.component.tsx +46 -0
  54. package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +38 -8
  55. package/src/client-registry/hie-client-registry/hie-resource.ts +126 -21
  56. package/src/client-registry/hie-client-registry/hie-types.ts +102 -0
  57. package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +78 -62
  58. package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +55 -3
  59. package/src/client-registry/hie-client-registry/modal/hie-otp-verification-form.component.tsx +88 -0
  60. package/src/client-registry/hie-client-registry/modal/hie-patient-detail-preview.component.tsx +77 -0
  61. package/src/client-registry/hie-client-registry/patient-info/patient-info.component.tsx +17 -0
  62. package/src/config-schema.ts +30 -2
  63. package/src/patient-registration/field/address/address-search.component.tsx +5 -2
  64. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
  65. package/src/patient-registration/field/dob/dob.component.tsx +1 -1
  66. package/src/patient-registration/field/field.scss +4 -4
  67. package/src/patient-registration/field/gender/gender-field.component.tsx +6 -2
  68. package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +3 -3
  69. package/src/patient-registration/field/name/name-field.component.tsx +2 -2
  70. package/src/patient-registration/field/obs/obs-field.component.tsx +9 -5
  71. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +4 -3
  72. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +22 -11
  73. package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +76 -27
  74. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +1 -1
  75. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +12 -4
  76. package/src/patient-registration/field/person-attributes/useUpdateIdentifierRequirement.tsx +83 -0
  77. package/src/patient-registration/form-manager.test.ts +4 -1
  78. package/src/patient-registration/form-manager.ts +0 -1
  79. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +52 -62
  80. package/src/patient-registration/mpi/mpi-patient.resource.ts +21 -0
  81. package/src/patient-registration/patient-registration-hooks.ts +90 -25
  82. package/src/patient-registration/patient-registration-utils.test.ts +33 -0
  83. package/src/patient-registration/patient-registration-utils.ts +63 -13
  84. package/src/patient-registration/patient-registration.component.tsx +17 -2
  85. package/src/patient-registration/patient-registration.test.tsx +442 -56
  86. package/src/patient-registration/section/demographics/demographics-section.component.tsx +3 -3
  87. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +1 -1
  88. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +28 -28
  89. package/src/widgets/cancel-patient-edit.modal.tsx +2 -0
  90. package/src/widgets/cancel-patient-edit.scss +29 -0
  91. package/src/widgets/delete-identifier-confirmation.modal.tsx +2 -0
  92. package/src/widgets/delete-identifier-confirmation.scss +29 -0
  93. package/translations/am.json +1 -0
  94. package/translations/ar.json +6 -4
  95. package/translations/de.json +118 -0
  96. package/translations/en.json +17 -0
  97. package/translations/es.json +2 -0
  98. package/translations/fr.json +1 -0
  99. package/translations/he.json +1 -0
  100. package/translations/hi.json +118 -0
  101. package/translations/hi_IN.json +118 -0
  102. package/translations/id.json +118 -0
  103. package/translations/it.json +118 -0
  104. package/translations/km.json +1 -0
  105. package/translations/ne.json +118 -0
  106. package/translations/pt.json +118 -0
  107. package/translations/pt_BR.json +118 -0
  108. package/translations/qu.json +118 -0
  109. package/translations/si.json +118 -0
  110. package/translations/sw.json +118 -0
  111. package/translations/sw_KE.json +118 -0
  112. package/translations/tr.json +118 -0
  113. package/translations/tr_TR.json +118 -0
  114. package/translations/uk.json +118 -0
  115. package/translations/vi.json +118 -0
  116. package/translations/zh.json +3 -1
  117. package/translations/zh_CN.json +2 -0
  118. package/dist/10.js +0 -1
  119. package/dist/10.js.map +0 -1
  120. package/dist/250.js +0 -1
  121. package/dist/250.js.map +0 -1
  122. package/dist/662.js +0 -1
  123. package/dist/662.js.map +0 -1
  124. package/dist/753.js +0 -1
  125. package/dist/753.js.map +0 -1
  126. package/dist/895.js +0 -2
  127. package/dist/895.js.map +0 -1
@@ -1,10 +1,62 @@
1
1
  @use '@carbon/type';
2
- @use '@openmrs/esm-styleguide/src/vars' as *;
2
+ @use '@carbon/layout';
3
+ @use '@carbon/colors';
3
4
 
4
5
  .header {
5
6
  @include type.type-style('heading-03');
6
7
  }
7
8
 
8
- .body {
9
- @include type.type-style('body-01');
9
+ .patientInfo {
10
+ display: grid;
11
+ grid-template-columns: 0.25fr 0.75fr;
12
+ margin: 0.25rem;
13
+ }
14
+
15
+ .patientInfoLabel {
16
+ min-width: 5rem;
17
+ font-weight: bold;
18
+ }
19
+
20
+ .dependentInfo {
21
+ margin-bottom: 1rem;
22
+ padding: 0.5rem;
23
+ border: 1px solid #ddd;
24
+ border-radius: 5px;
25
+ }
26
+
27
+ .patientDetails {
28
+ display: flex;
29
+ margin: 1rem;
30
+ }
31
+
32
+ .patientPhotoContainer {
33
+ display: flex;
34
+ align-items: center;
35
+ }
36
+
37
+ .patientInfoContainer {
38
+ width: 100%;
39
+ margin-left: 0.625rem;
40
+ }
41
+
42
+ .patientNameValue {
43
+ display: flex;
44
+ gap: layout.$spacing-03;
45
+ align-items: center;
46
+
47
+ & > span {
48
+ color: colors.$gray-40;
49
+ }
50
+ }
51
+
52
+ .grid {
53
+ margin: 0 layout.$spacing-05;
54
+ padding: layout.$spacing-05 0rem 0rem orem;
55
+ }
56
+
57
+ .otpInputRow {
58
+ display: flex;
59
+ flex-direction: row;
60
+ gap: layout.$spacing-03;
61
+ align-items: flex-end;
10
62
  }
@@ -0,0 +1,88 @@
1
+ import { Button, Column, Row, Stack, Tag, TextInput, InlineLoading } from '@carbon/react';
2
+ import { showSnackbar } from '@openmrs/esm-framework';
3
+ import React from 'react';
4
+ import { Controller, useFormContext } from 'react-hook-form';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { type authorizationFormSchema, generateOTP, persistOTP, sendOtp } from '../hie-resource';
7
+ import styles from './confirm-hie.scss';
8
+ import { type z } from 'zod';
9
+
10
+ type HIEOTPVerficationFormProps = {
11
+ name: string;
12
+ patientId: string;
13
+ status?: 'loadingOtp' | 'otpSendSuccessfull' | 'otpFetchError';
14
+ setStatus: React.Dispatch<React.SetStateAction<'loadingOtp' | 'otpSendSuccessfull' | 'otpFetchError'>>;
15
+ };
16
+
17
+ const HIEOTPVerficationForm: React.FC<HIEOTPVerficationFormProps> = ({ name, patientId, setStatus, status }) => {
18
+ const form = useFormContext<z.infer<typeof authorizationFormSchema>>();
19
+ const { t } = useTranslation();
20
+
21
+ const handleGetOTP = async () => {
22
+ try {
23
+ setStatus('loadingOtp');
24
+ const otp = generateOTP(5);
25
+ await sendOtp({ otp, receiver: form.watch('receiver') }, name);
26
+ setStatus('otpSendSuccessfull');
27
+ persistOTP(otp, patientId);
28
+ } catch (error) {
29
+ setStatus('otpFetchError');
30
+ showSnackbar({ title: t('error', 'Error'), kind: 'error', subtitle: error?.message });
31
+ }
32
+ };
33
+
34
+ return (
35
+ <Stack gap={4} className={styles.grid}>
36
+ <Column>
37
+ <Controller
38
+ control={form.control}
39
+ name="receiver"
40
+ render={({ field }) => (
41
+ <TextInput
42
+ invalid={form.formState.errors[field.name]?.message}
43
+ invalidText={form.formState.errors[field.name]?.message}
44
+ {...field}
45
+ placeholder={t('patientPhoneNUmber', 'Patient Phone number')}
46
+ labelText={t('patientPhoneNUmber', 'Patient Phone number')}
47
+ helperText={t('phoneNumberHelper', 'Patient will receive OTP on this number')}
48
+ />
49
+ )}
50
+ />
51
+ </Column>
52
+
53
+ <Column>
54
+ <Controller
55
+ control={form.control}
56
+ name="otp"
57
+ render={({ field }) => (
58
+ <Row className={styles.otpInputRow}>
59
+ <TextInput
60
+ invalid={form.formState.errors[field.name]?.message}
61
+ invalidText={form.formState.errors[field.name]?.message}
62
+ {...field}
63
+ placeholder={t('otpCode', 'OTP Authorization code')}
64
+ labelText={t('otpCode', 'OTP Authorization code')}
65
+ />
66
+ <Button
67
+ onClick={handleGetOTP}
68
+ role="button"
69
+ type="blue"
70
+ kind="tertiary"
71
+ disabled={['loadingOtp', 'otpSendSuccessfull'].includes(status)}>
72
+ {status === 'loadingOtp' ? (
73
+ <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />
74
+ ) : status === 'otpFetchError' ? (
75
+ t('retry', 'Retry')
76
+ ) : (
77
+ t('verifyOTP', 'Verify with OTP')
78
+ )}
79
+ </Button>
80
+ </Row>
81
+ )}
82
+ />
83
+ </Column>
84
+ </Stack>
85
+ );
86
+ };
87
+
88
+ export default HIEOTPVerficationForm;
@@ -0,0 +1,77 @@
1
+ import { Accordion, AccordionItem, CodeSnippet } from '@carbon/react';
2
+ import { age, ExtensionSlot, formatDate } from '@openmrs/esm-framework';
3
+ import capitalize from 'lodash-es/capitalize';
4
+ import React from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import DependentInfo from '../dependants/dependants.component';
7
+ import { getPatientName, maskData } from '../hie-resource';
8
+ import PatientInfo from '../patient-info/patient-info.component';
9
+ import styles from './confirm-hie.scss';
10
+ import { type HIEPatientResponse } from '../hie-types';
11
+
12
+ type HIEPatientDetailPreviewProps = {
13
+ patient: HIEPatientResponse;
14
+ };
15
+
16
+ const HIEPatientDetailPreview: React.FC<HIEPatientDetailPreviewProps> = ({ patient }) => {
17
+ const { familyName, givenName, middleName } = getPatientName(patient);
18
+ const { t } = useTranslation();
19
+ const getidentifier = (code: string) =>
20
+ patient?.entry[0]?.resource.identifier?.find(
21
+ (identifier) => identifier?.type?.coding?.some((coding) => coding?.code === code),
22
+ );
23
+
24
+ return (
25
+ <>
26
+ <div className={styles.patientDetails}>
27
+ <ExtensionSlot
28
+ className={styles.patientPhotoContainer}
29
+ name="patient-photo-slot"
30
+ state={{ patientName: `${maskData(givenName)} . ${maskData(middleName)} . ${maskData(familyName)}` }}
31
+ />
32
+ <div className={styles.patientInfoContainer}>
33
+ <PatientInfo label={t('healthID', 'HealthID')} value={getidentifier('sha-number')?.value} />
34
+ <PatientInfo
35
+ label={t('patientName', 'Patient name')}
36
+ customValue={
37
+ <span className={styles.patientNameValue}>
38
+ <p>{maskData(givenName)}</p>
39
+ <span>&bull;</span>
40
+ <p>{maskData(middleName)}</p>
41
+ <span>&bull;</span>
42
+ <p>{maskData(familyName)}</p>
43
+ </span>
44
+ }
45
+ />
46
+
47
+ <PatientInfo label={t('age', 'Age')} value={age(patient?.entry[0]?.resource.birthDate)} />
48
+ <PatientInfo
49
+ label={t('dateOfBirth', 'Date of birth')}
50
+ value={formatDate(new Date(patient?.entry[0]?.resource?.birthDate))}
51
+ />
52
+ <PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.entry[0]?.resource.gender)} />
53
+ <PatientInfo
54
+ label={t('maritalStatus', 'Marital status')}
55
+ value={patient?.entry[0]?.resource.maritalStatus?.coding?.map((m) => m.code).join('')}
56
+ />
57
+
58
+ {!patient?.entry[0]?.resource.contact && <PatientInfo label={t('dependents', 'Dependents')} value="--" />}
59
+ </div>
60
+ </div>
61
+
62
+ <DependentInfo dependents={patient?.entry[0]?.resource.contact} />
63
+
64
+ <div>
65
+ <Accordion>
66
+ <AccordionItem title={t('viewFullResponse', 'View full response')}>
67
+ <CodeSnippet type="multi" feedback="Copied to clipboard">
68
+ {JSON.stringify(patient, null, 2)}
69
+ </CodeSnippet>
70
+ </AccordionItem>
71
+ </Accordion>
72
+ </div>
73
+ </>
74
+ );
75
+ };
76
+
77
+ export default HIEPatientDetailPreview;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import styles from '../modal/confirm-hie.scss';
3
+
4
+ const PatientInfo: React.FC<{ label: string; value?: string; customValue?: JSX.Element }> = ({
5
+ label,
6
+ value,
7
+ customValue,
8
+ }) => {
9
+ return (
10
+ <div className={styles.patientInfo}>
11
+ <span className={styles.patientInfoLabel}>{label}</span>
12
+ <span>{value || customValue || '--'}</span>
13
+ </div>
14
+ );
15
+ };
16
+
17
+ export default PatientInfo;
@@ -1,4 +1,5 @@
1
1
  import { Type, validator, validators } from '@openmrs/esm-framework';
2
+ import _default from 'yup/lib/locale';
2
3
 
3
4
  export interface SectionDefinition {
4
5
  id: string;
@@ -12,7 +13,8 @@ export interface FieldDefinition {
12
13
  label?: string;
13
14
  uuid: string;
14
15
  placeholder?: string;
15
- dateFormat?: string;
16
+ allowFutureDates?: boolean;
17
+ allowPastDates?: boolean;
16
18
  showHeading: boolean;
17
19
  validation?: {
18
20
  required: boolean;
@@ -71,6 +73,7 @@ export interface RegistrationConfig {
71
73
  month: number;
72
74
  };
73
75
  };
76
+ identifierMappings: [{ fhirIdentifierSystem: string; openmrsIdentifierTypeUuid: string }];
74
77
  phone: {
75
78
  personAttributeUuid: string;
76
79
  validation?: {
@@ -186,6 +189,16 @@ export const esmPatientRegistrationSchema = {
186
189
  _default: '',
187
190
  _description: 'Placeholder that will appear in the input.',
188
191
  },
192
+ allowFutureDates: {
193
+ _type: Type.Boolean,
194
+ _default: true,
195
+ _description: 'Indicates whether the date input field should allow the selection of future dates or not.',
196
+ },
197
+ allowPastDates: {
198
+ _type: Type.Boolean,
199
+ _default: true,
200
+ _description: 'Indicates whether the date input field should allow the selection of past dates or not.',
201
+ },
189
202
  validation: {
190
203
  required: { _type: Type.Boolean, _default: false },
191
204
  matches: {
@@ -349,6 +362,21 @@ export const esmPatientRegistrationSchema = {
349
362
  },
350
363
  },
351
364
  },
365
+ identifierMappings: {
366
+ _type: Type.Array,
367
+ _elements: {
368
+ fhirIdentifierSystem: {
369
+ _type: Type.String,
370
+ _description: 'Identifier system from the fhir server',
371
+ },
372
+ openmrsIdentifierTypeUuid: {
373
+ _type: Type.String,
374
+ _default: null,
375
+ _description: 'Identifier type uuid of OpenMRS to map the identifier system',
376
+ },
377
+ },
378
+ _default: [],
379
+ },
352
380
  phone: {
353
381
  personAttributeUuid: {
354
382
  _type: Type.UUID,
@@ -416,7 +444,7 @@ export const esmPatientRegistrationSchema = {
416
444
  },
417
445
  },
418
446
  _default: [
419
- { identifierType: 'National ID', identifierValue: 'national-id' },
447
+ { identifierType: 'National ID', identifierValue: 'National ID' },
420
448
  { identifierType: 'Passport Number', identifierValue: 'passport-number' },
421
449
  { identifierType: 'Birth Certificate Number', identifierValue: 'birth-certificate-number' },
422
450
  { identifierType: 'Alien ID Number', identifierValue: 'alien-id-number' },
@@ -14,8 +14,11 @@ const AddressSearchComponent: React.FC<AddressSearchComponentProps> = ({ address
14
14
  const separator = ' > ';
15
15
  const searchBox = useRef(null);
16
16
  const wrapper = useRef(null);
17
- const [searchString, setSearchString] = useState<string>('');
18
- const { addresses, isLoading, error } = useAddressHierarchy(searchString, separator);
17
+
18
+ const [searchString, setSearchString] = useState('');
19
+
20
+ const { addresses } = useAddressHierarchy(searchString, separator);
21
+
19
22
  const addressOptions: Array<string> = useMemo(() => {
20
23
  const options: Set<string> = new Set();
21
24
  addresses.forEach((address) => {
@@ -35,7 +35,7 @@ function DeathDateField() {
35
35
  selectedDate ? dayjs(selectedDate).hour(0).minute(0).second(0).millisecond(0).toDate() : undefined,
36
36
  );
37
37
  },
38
- [deathDate],
38
+ [setFieldValue],
39
39
  );
40
40
 
41
41
  return (
@@ -41,7 +41,7 @@ export const DobField: React.FC = () => {
41
41
  setFieldValue('monthsEstimated', '');
42
42
  setFieldTouched('birthdateEstimated', true, false);
43
43
  },
44
- [setFieldValue],
44
+ [setFieldTouched, setFieldValue],
45
45
  );
46
46
 
47
47
  const onDateChange = useCallback(
@@ -111,10 +111,6 @@
111
111
  svg {
112
112
  margin-left: layout.$spacing-03;
113
113
  }
114
-
115
- .customField {
116
- margin-bottom: layout.$spacing-05;
117
- }
118
114
  }
119
115
 
120
116
  .attributeField {
@@ -169,3 +165,7 @@
169
165
  line-height: 1.34;
170
166
  margin: layout.$spacing-02 0 0;
171
167
  }
168
+
169
+ .customField {
170
+ margin-bottom: layout.$spacing-05;
171
+ }
@@ -30,8 +30,12 @@ export const GenderField: React.FC = () => {
30
30
  <div className={styles.halfWidthInDesktopView}>
31
31
  <h4 className={styles.productiveHeading02Light}>{t('sexFieldLabelText', 'Sex')}</h4>
32
32
  <div className={styles.sexField}>
33
- <p className="cds--label">{t('genderLabelText', 'Sex')}</p>
34
- <RadioButtonGroup name="gender" orientation="vertical" onChange={setGender} valueSelected={field.value}>
33
+ <RadioButtonGroup
34
+ name="gender"
35
+ legendText={t('genderLabelText', 'Sex')}
36
+ orientation="vertical"
37
+ onChange={setGender}
38
+ valueSelected={field.value}>
35
39
  {fieldConfigs.map((option) => (
36
40
  <RadioButton
37
41
  key={option.label ?? option.value}
@@ -23,7 +23,7 @@ const PatientIdentifierOverlay: React.FC<PatientIdentifierOverlayProps> = ({ clo
23
23
  const { identifierTypes } = useContext(ResourcesContext);
24
24
  const { isOffline, values, initialFormValues } = useContext(PatientRegistrationContext);
25
25
  const [unsavedIdentifierTypes, setUnsavedIdentifierTypes] = useState<FormValues['identifiers']>(values.identifiers);
26
- const [searchString, setSearchString] = useState<string>('');
26
+ const [searchString, setSearchString] = useState('');
27
27
  const { t } = useTranslation();
28
28
  const { defaultPatientIdentifierTypes } = useConfig();
29
29
  const defaultPatientIdentifierTypesMap = useMemo(() => {
@@ -107,11 +107,11 @@ const PatientIdentifierOverlay: React.FC<PatientIdentifierOverlayProps> = ({ clo
107
107
  />
108
108
  {patientIdentifier &&
109
109
  identifierType?.identifierSources?.length > 0 &&
110
- /*
110
+ /*
111
111
  This check are for the cases when there's an initialValue identifier is assigned
112
112
  to the patient
113
113
  The corresponding flow is like:
114
- 1. If there's no change to the actual initial identifier, then the source remains null,
114
+ 1. If there's no change to the actual initial identifier, then the source remains null,
115
115
  hence the list of the identifier sources shouldn't be displayed.
116
116
  2. If user wants to edit the patient identifier's value, hence there will be an initialValue,
117
117
  along with a source assigned to itself(only if the identifierType has sources, else there's nothing to worry about), which by
@@ -3,9 +3,9 @@ import { useTranslation } from 'react-i18next';
3
3
  import { ContentSwitcher, Switch } from '@carbon/react';
4
4
  import { useField } from 'formik';
5
5
  import { ExtensionSlot, useConfig } from '@openmrs/esm-framework';
6
+ import { type RegistrationConfig } from '../../../config-schema';
6
7
  import { Input } from '../../input/basic-input/input/input.component';
7
8
  import { PatientRegistrationContext } from '../../patient-registration-context';
8
- import { type RegistrationConfig } from '../../../config-schema';
9
9
  import styles from '../field.scss';
10
10
 
11
11
  export const unidentifiedPatientAttributeTypeUuid = '8b56eac7-5c76-4b9c-8c6f-1deab8d3fc47';
@@ -51,7 +51,7 @@ export const NameField = () => {
51
51
  setFieldTouched('photo', true, false);
52
52
  }
53
53
  },
54
- [setCapturePhotoProps],
54
+ [setCapturePhotoProps, setFieldTouched],
55
55
  );
56
56
 
57
57
  const toggleNameKnown = (e) => {
@@ -57,8 +57,8 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) {
57
57
  concept={concept}
58
58
  label={fieldDefinition.label}
59
59
  required={fieldDefinition.validation.required}
60
- dateFormat={fieldDefinition.dateFormat}
61
- placeholder={fieldDefinition.placeholder}
60
+ allowPastDates={fieldDefinition.allowPastDates}
61
+ allowFutureDates={fieldDefinition.allowFutureDates}
62
62
  />
63
63
  );
64
64
  case 'Coded':
@@ -159,14 +159,16 @@ interface DateObsFieldProps {
159
159
  concept: ConceptResponse;
160
160
  label: string;
161
161
  required?: boolean;
162
- dateFormat?: string;
163
- placeholder?: string;
162
+ allowPastDates?: boolean;
163
+ allowFutureDates?: boolean;
164
164
  }
165
165
 
166
- function DateObsField({ concept, label, required, placeholder }: DateObsFieldProps) {
166
+ function DateObsField({ concept, label, required, allowPastDates, allowFutureDates }: DateObsFieldProps) {
167
167
  const { t } = useTranslation();
168
168
  const fieldName = `obs.${concept.uuid}`;
169
169
  const { setFieldValue } = useContext(PatientRegistrationContext);
170
+ const futureDatesAllowed = allowFutureDates ?? true;
171
+ const pastDatesAllowed = allowPastDates ?? true;
170
172
 
171
173
  const onDateChange = (date: Date) => {
172
174
  setFieldValue(fieldName, date);
@@ -188,6 +190,8 @@ function DateObsField({ concept, label, required, placeholder }: DateObsFieldPro
188
190
  isInvalid={errors[fieldName] && touched[fieldName]}
189
191
  invalidText={t(meta.error)}
190
192
  value={field.value}
193
+ minDate={!pastDatesAllowed ? new Date() : undefined}
194
+ maxDate={!futureDatesAllowed ? new Date() : undefined}
191
195
  />
192
196
  </>
193
197
  );
@@ -3,10 +3,10 @@ import classNames from 'classnames';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Field } from 'formik';
5
5
  import { Layer, Select, SelectItem } from '@carbon/react';
6
+ import { reportError } from '@openmrs/esm-framework';
6
7
  import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
7
8
  import { useConceptAnswers } from '../field.resource';
8
9
  import styles from './../field.scss';
9
- import { reportError } from '@openmrs/esm-framework';
10
10
 
11
11
  export interface CodedPersonAttributeFieldProps {
12
12
  id: string;
@@ -44,7 +44,7 @@ export function CodedPersonAttributeField({
44
44
  );
45
45
  setError(true);
46
46
  }
47
- }, [answerConceptSetUuid, customConceptAnswers]);
47
+ }, [answerConceptSetUuid, customConceptAnswers, id, t]);
48
48
 
49
49
  useEffect(() => {
50
50
  if (!isLoadingConceptAnswers && !customConceptAnswers.length) {
@@ -72,7 +72,7 @@ export function CodedPersonAttributeField({
72
72
  setError(true);
73
73
  }
74
74
  }
75
- }, [isLoadingConceptAnswers, conceptAnswers, customConceptAnswers]);
75
+ }, [isLoadingConceptAnswers, conceptAnswers, customConceptAnswers, t, id, answerConceptSetUuid]);
76
76
 
77
77
  const answers = useMemo(() => {
78
78
  if (customConceptAnswers.length) {
@@ -98,6 +98,7 @@ export function CodedPersonAttributeField({
98
98
  return (
99
99
  <>
100
100
  <Select
101
+ style={{ marginBottom: '1rem' }}
101
102
  id={id}
102
103
  name={`person-attribute-${personAttributeType.uuid}`}
103
104
  labelText={label ?? personAttributeType?.display}
@@ -4,19 +4,19 @@ import { render, screen } from '@testing-library/react';
4
4
  import { useConceptAnswers } from '../field.resource';
5
5
  import { CodedPersonAttributeField } from './coded-person-attribute-field.component';
6
6
 
7
- jest.mock('formik', () => ({
8
- ...jest.requireActual('formik'),
9
- }));
10
-
11
- jest.mock('../field.resource');
12
-
13
7
  const mockUseConceptAnswers = jest.mocked(useConceptAnswers);
14
8
 
9
+ jest.mock('../field.resource', () => ({
10
+ ...jest.requireActual('../field.resource'),
11
+ useConceptAnswers: jest.fn(),
12
+ }));
13
+
15
14
  describe('CodedPersonAttributeField', () => {
16
15
  const conceptAnswers = [
17
16
  { uuid: '1', display: 'Option 1' },
18
17
  { uuid: '2', display: 'Option 2' },
19
18
  ];
19
+
20
20
  const personAttributeType = {
21
21
  format: 'org.openmrs.Concept',
22
22
  display: 'Referred by',
@@ -24,26 +24,35 @@ describe('CodedPersonAttributeField', () => {
24
24
  name: '',
25
25
  description: '',
26
26
  };
27
+
27
28
  const answerConceptSetUuid = '6682d17f-0777-45e4-a39b-93f77eb3531c';
29
+ let consoleSpy: jest.SpyInstance;
28
30
 
29
31
  beforeEach(() => {
30
32
  mockUseConceptAnswers.mockReturnValue({
31
33
  data: conceptAnswers,
32
34
  isLoading: false,
35
+ error: null,
33
36
  });
37
+
38
+ consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
34
39
  });
35
40
 
36
- it('shows error if there is no concept answer set provided', () => {
41
+ afterEach(() => {
42
+ consoleSpy.mockRestore();
43
+ });
44
+
45
+ it('renders an error if there is no concept answer set provided', () => {
37
46
  expect(() => {
38
47
  render(
39
48
  <Formik initialValues={{}} onSubmit={() => {}}>
40
49
  <Form>
41
50
  <CodedPersonAttributeField
42
- id="attributeId"
43
- personAttributeType={personAttributeType}
44
51
  answerConceptSetUuid={null}
45
- label={personAttributeType.display}
46
52
  customConceptAnswers={[]}
53
+ id="attributeId"
54
+ label={personAttributeType.display}
55
+ personAttributeType={personAttributeType}
47
56
  required={false}
48
57
  />
49
58
  </Form>
@@ -52,11 +61,13 @@ describe('CodedPersonAttributeField', () => {
52
61
  }).toThrow(expect.stringMatching(/has been defined without an answer concept set UUID/i));
53
62
  });
54
63
 
55
- it('shows error if the concept answer set does not have any concept answers', () => {
64
+ it('renders an error if the concept answer set does not have any concept answers', () => {
56
65
  mockUseConceptAnswers.mockReturnValue({
57
66
  data: [],
58
67
  isLoading: false,
68
+ error: null,
59
69
  });
70
+
60
71
  expect(() => {
61
72
  render(
62
73
  <Formik initialValues={{}} onSubmit={() => {}}>