@kenyaemr/esm-patient-registration-app 6.0.2 → 7.0.2-pre.65

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 (66) hide show
  1. package/.turbo/turbo-build.log +38 -0
  2. package/dist/130.js +1 -1
  3. package/dist/130.js.map +1 -1
  4. package/dist/152.js +1 -1
  5. package/dist/152.js.map +1 -1
  6. package/dist/169.js +1 -0
  7. package/dist/169.js.map +1 -0
  8. package/dist/207.js +1 -0
  9. package/dist/{537.js.map → 207.js.map} +1 -1
  10. package/dist/255.js +1 -1
  11. package/dist/255.js.map +1 -1
  12. package/dist/266.js +2 -0
  13. package/dist/266.js.LICENSE.txt +14 -0
  14. package/dist/266.js.map +1 -0
  15. package/dist/303.js +1 -1
  16. package/dist/303.js.map +1 -1
  17. package/dist/330.js +1 -1
  18. package/dist/501.js +1 -0
  19. package/dist/501.js.map +1 -0
  20. package/dist/548.js +1 -0
  21. package/dist/548.js.map +1 -0
  22. package/dist/574.js +1 -1
  23. package/dist/59.js +1 -1
  24. package/dist/59.js.map +1 -1
  25. package/dist/729.js +1 -1
  26. package/dist/729.js.map +1 -1
  27. package/dist/735.js +1 -1
  28. package/dist/753.js +1 -0
  29. package/dist/753.js.map +1 -0
  30. package/dist/879.js +1 -1
  31. package/dist/879.js.map +1 -1
  32. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  33. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +136 -112
  34. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  35. package/dist/main.js +1 -1
  36. package/dist/main.js.LICENSE.txt +0 -15
  37. package/dist/main.js.map +1 -1
  38. package/dist/routes.json +1 -1
  39. package/package.json +5 -4
  40. package/src/config-schema.ts +6 -0
  41. package/src/offline.resources.ts +7 -5
  42. package/src/patient-registration/field/custom-field.component.tsx +6 -0
  43. package/src/patient-registration/field/dob/dob.component.tsx +17 -14
  44. package/src/patient-registration/field/dob/dob.test.tsx +1 -0
  45. package/src/patient-registration/field/field.test.tsx +1 -0
  46. package/src/patient-registration/field/obs/obs-field.component.tsx +64 -2
  47. package/src/patient-registration/field/obs/obs-field.test.tsx +44 -2
  48. package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +56 -0
  49. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +21 -7
  50. package/src/patient-registration/form-manager.ts +7 -5
  51. package/src/patient-registration/patient-registration.component.tsx +5 -4
  52. package/src/patient-registration/patient-registration.resource.ts +1 -1
  53. package/src/patient-registration/patient-registration.scss +16 -13
  54. package/src/patient-registration/patient-registration.test.tsx +6 -6
  55. package/src/patient-registration/section/demographics/demographics-section.test.tsx +1 -0
  56. package/translations/en.json +5 -5
  57. package/dist/481.js +0 -1
  58. package/dist/481.js.map +0 -1
  59. package/dist/537.js +0 -1
  60. package/dist/676.js +0 -1
  61. package/dist/676.js.map +0 -1
  62. package/dist/762.js +0 -2
  63. package/dist/762.js.LICENSE.txt +0 -29
  64. package/dist/762.js.map +0 -1
  65. package/dist/816.js +0 -1
  66. package/dist/816.js.map +0 -1
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true},{"component":"cancelPatientEditModal","name":"cancel-patient-edit-modal","online":true,"offline":true},{"component":"patientPhotoExtension","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true},{"component":"deleteIdentifierConfirmationModal","name":"delete-identifier-confirmation-modal","online":true,"offline":true},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal","online":true,"offline":true},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal","online":true,"offline":true}],"version":"6.0.1-pre.1.0.6"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true},{"component":"cancelPatientEditModal","name":"cancel-patient-edit-modal","online":true,"offline":true},{"component":"patientPhotoExtension","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true},{"component":"deleteIdentifierConfirmationModal","name":"delete-identifier-confirmation-modal","online":true,"offline":true},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal","online":true,"offline":true},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal","online":true,"offline":true}],"version":"7.0.2-pre.65"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-patient-registration-app",
3
- "version": "6.0.2",
3
+ "version": "7.0.2-pre.65",
4
4
  "description": "Patient registration microfrontend for the OpenMRS SPA",
5
5
  "browser": "dist/kenyaemr-esm-patient-registration-app.js",
6
6
  "main": "src/index.ts",
@@ -18,7 +18,7 @@
18
18
  "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
19
19
  "coverage": "yarn test --coverage",
20
20
  "typescript": "tsc",
21
- "extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts'"
21
+ "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
22
22
  },
23
23
  "browserslist": [
24
24
  "extends browserslist-config-openmrs"
@@ -53,5 +53,6 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "webpack": "^5.74.0"
56
- }
57
- }
56
+ },
57
+ "stableVersion": "7.0.1"
58
+ }
@@ -12,6 +12,7 @@ export interface FieldDefinition {
12
12
  label?: string;
13
13
  uuid: string;
14
14
  placeholder?: string;
15
+ dateFormat?: string;
15
16
  showHeading: boolean;
16
17
  validation?: {
17
18
  required: boolean;
@@ -19,6 +20,11 @@ export interface FieldDefinition {
19
20
  };
20
21
  answerConceptSetUuid?: string;
21
22
  customConceptAnswers?: Array<CustomConceptAnswer>;
23
+ showWhenExpression?: {
24
+ field: string;
25
+ value: string;
26
+ };
27
+ renderType?: string;
22
28
  }
23
29
  export interface CustomConceptAnswer {
24
30
  uuid: string;
@@ -75,13 +75,15 @@ export async function fetchPatientIdentifierTypesWithSources(): Promise<Array<Pa
75
75
  // @ts-ignore Reason: The required props of the type are generated below.
76
76
  const identifierTypes: Array<PatientIdentifierType> = patientIdentifierTypes.filter(Boolean);
77
77
 
78
- const [autoGenOptions, ...allIdentifierSources] = await Promise.all([
78
+ const [autoGenOptions, identifierSourcesResponse] = await Promise.all([
79
79
  fetchAutoGenerationOptions(),
80
- ...identifierTypes.map((identifierType) => fetchIdentifierSources(identifierType.uuid)),
80
+ fetchIdentifierSources(),
81
81
  ]);
82
82
 
83
+ const allIdentifierSources = identifierSourcesResponse.data.results;
84
+
83
85
  for (let i = 0; i < identifierTypes?.length; i++) {
84
- identifierTypes[i].identifierSources = allIdentifierSources[i].data.results.map((source) => {
86
+ identifierTypes[i].identifierSources = allIdentifierSources.map((source) => {
85
87
  const option = find(autoGenOptions.data.results, { source: { uuid: source.uuid } });
86
88
  source.autoGenerationOption = option;
87
89
  return source;
@@ -118,8 +120,8 @@ async function fetchPatientIdentifierTypes(): Promise<Array<FetchedPatientIdenti
118
120
  return [];
119
121
  }
120
122
 
121
- async function fetchIdentifierSources(identifierType: string) {
122
- return await cacheAndFetch(`${restBaseUrl}/idgen/identifiersource?v=default&identifierType=${identifierType}`);
123
+ async function fetchIdentifierSources() {
124
+ return await cacheAndFetch(`${restBaseUrl}/idgen/identifiersource?v=default`);
123
125
  }
124
126
 
125
127
  async function fetchAutoGenerationOptions(abortController?: AbortController) {
@@ -4,6 +4,7 @@ import { type RegistrationConfig } from '../../config-schema';
4
4
  import { AddressField } from './address/custom-address-field.component';
5
5
  import { ObsField } from './obs/obs-field.component';
6
6
  import { PersonAttributeField } from './person-attributes/person-attribute-field.component';
7
+ import { useField } from 'formik';
7
8
 
8
9
  export interface CustomFieldProps {
9
10
  name: string;
@@ -13,6 +14,11 @@ export function CustomField({ name }: CustomFieldProps) {
13
14
  const config = useConfig() as RegistrationConfig;
14
15
  const fieldDefinition = config.fieldDefinitions.filter((def) => def.id == name)[0];
15
16
 
17
+ const [{ value }] = useField(`attributes.${fieldDefinition.showWhenExpression?.field}`);
18
+ if (fieldDefinition.showWhenExpression && value !== fieldDefinition.showWhenExpression.value) {
19
+ return null;
20
+ }
21
+
16
22
  if (fieldDefinition.type === 'person attribute') {
17
23
  return <PersonAttributeField fieldDefinition={fieldDefinition} />;
18
24
  } else if (fieldDefinition.type === 'obs') {
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
4
4
  import { useField } from 'formik';
5
5
  import { generateFormatting } from '../../date-util';
6
6
  import { PatientRegistrationContext } from '../../patient-registration-context';
7
- import { useConfig } from '@openmrs/esm-framework';
7
+ import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework';
8
8
  import { type RegistrationConfig } from '../../../config-schema';
9
9
  import styles from '../field.scss';
10
10
 
@@ -46,8 +46,8 @@ export const DobField: React.FC = () => {
46
46
  );
47
47
 
48
48
  const onDateChange = useCallback(
49
- (birthdate: Date[]) => {
50
- setFieldValue('birthdate', birthdate[0]);
49
+ (birthdate: Date) => {
50
+ setFieldValue('birthdate', birthdate);
51
51
  },
52
52
  [setFieldValue],
53
53
  );
@@ -101,17 +101,20 @@ export const DobField: React.FC = () => {
101
101
  <Layer>
102
102
  {!dobUnknown ? (
103
103
  <div className={styles.dobField}>
104
- <DatePicker dateFormat={dateFormat} datePickerType="single" onChange={onDateChange} maxDate={format(today)}>
105
- <DatePickerInput
106
- id="birthdate"
107
- {...birthdate}
108
- placeholder={placeHolder}
109
- labelText={t('dateOfBirthLabelText', 'Date of Birth')}
110
- invalid={!!(birthdateMeta.touched && birthdateMeta.error)}
111
- invalidText={birthdateMeta.error && t(birthdateMeta.error)}
112
- value={format(birthdate.value)}
113
- />
114
- </DatePicker>
104
+ <OpenmrsDatePicker
105
+ id="birthdate"
106
+ {...birthdate}
107
+ dateFormat={dateFormat}
108
+ onChange={onDateChange}
109
+ maxDate={today}
110
+ labelText={t('dateOfBirthLabelText', 'Date of Birth')}
111
+ invalid={!!(birthdateMeta.touched && birthdateMeta.error)}
112
+ invalidText={birthdateMeta.error && t(birthdateMeta.error)}
113
+ value={birthdate.value}
114
+ carbonOptions={{
115
+ placeholder: placeHolder,
116
+ }}
117
+ />
115
118
  </div>
116
119
  ) : (
117
120
  <div className={styles.grid}>
@@ -19,6 +19,7 @@ jest.mock('@openmrs/esm-framework', () => {
19
19
  },
20
20
  },
21
21
  })),
22
+ getLocale: jest.fn().mockReturnValue('en'),
22
23
  };
23
24
  });
24
25
 
@@ -10,6 +10,7 @@ import { PatientRegistrationContext } from '../patient-registration-context';
10
10
  jest.mock('@openmrs/esm-framework', () => ({
11
11
  ...jest.requireActual('@openmrs/esm-framework'),
12
12
  useConfig: jest.fn(),
13
+ getLocale: jest.fn().mockReturnValue('en'),
13
14
  }));
14
15
 
15
16
  const predefinedAddressTemplate = {
@@ -1,14 +1,16 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useCallback, useContext, useMemo } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { Field } from 'formik';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { InlineNotification, Layer, Select, SelectItem } from '@carbon/react';
6
- import { useConfig } from '@openmrs/esm-framework';
6
+ import { OpenmrsDatePicker, parseDate, useConfig } from '@openmrs/esm-framework';
7
7
  import { type ConceptResponse } from '../../patient-registration.types';
8
8
  import { type FieldDefinition, type RegistrationConfig } from '../../../config-schema';
9
9
  import { Input } from '../../input/basic-input/input/input.component';
10
10
  import { useConcept, useConceptAnswers } from '../field.resource';
11
11
  import styles from './../field.scss';
12
+ import { PatientRegistrationContext } from '../../patient-registration-context';
13
+ import { generateFormatting } from '../../date-util';
12
14
 
13
15
  export interface ObsFieldProps {
14
16
  fieldDefinition: FieldDefinition;
@@ -51,6 +53,16 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) {
51
53
  required={fieldDefinition.validation.required}
52
54
  />
53
55
  );
56
+ case 'Date':
57
+ return (
58
+ <DateObsField
59
+ concept={concept}
60
+ label={fieldDefinition.label}
61
+ required={fieldDefinition.validation.required}
62
+ dateFormat={fieldDefinition.dateFormat}
63
+ placeholder={fieldDefinition.placeholder}
64
+ />
65
+ );
54
66
  case 'Coded':
55
67
  return (
56
68
  <CodedObsField
@@ -145,6 +157,56 @@ function NumericObsField({ concept, label, required }: NumericObsFieldProps) {
145
157
  );
146
158
  }
147
159
 
160
+ interface DateObsFieldProps {
161
+ concept: ConceptResponse;
162
+ label: string;
163
+ required?: boolean;
164
+ dateFormat?: string;
165
+ placeholder?: string;
166
+ }
167
+
168
+ function DateObsField({ concept, label, required, placeholder }: DateObsFieldProps) {
169
+ const { t } = useTranslation();
170
+ const fieldName = `obs.${concept.uuid}`;
171
+ const { setFieldValue } = useContext(PatientRegistrationContext);
172
+ const { format, placeHolder, dateFormat } = generateFormatting(['d', 'm', 'Y'], '/');
173
+
174
+ const onDateChange = useCallback(
175
+ (date: Date) => {
176
+ setFieldValue(fieldName, date);
177
+ },
178
+ [setFieldValue],
179
+ );
180
+
181
+ return (
182
+ <Layer>
183
+ <div className={styles.dobField}>
184
+ <Field name={fieldName}>
185
+ {({ field, form: { touched, errors }, meta }) => {
186
+ const dateValue = field.value ? parseDate(field.value) : field.value;
187
+ return (
188
+ <OpenmrsDatePicker
189
+ id={fieldName}
190
+ {...field}
191
+ required={required}
192
+ dateFormat={dateFormat}
193
+ onChange={onDateChange}
194
+ labelText={label ?? concept.display}
195
+ invalid={errors[fieldName] && touched[fieldName]}
196
+ invalidText={meta.error && t(meta.error)}
197
+ value={dateValue}
198
+ carbonOptions={{
199
+ placeholder: placeholder ?? placeHolder,
200
+ }}
201
+ />
202
+ );
203
+ }}
204
+ </Field>
205
+ </div>
206
+ </Layer>
207
+ );
208
+ }
209
+
148
210
  interface CodedObsFieldProps {
149
211
  concept: ConceptResponse;
150
212
  answerConceptSetUuid?: string;
@@ -5,6 +5,7 @@ import { useConfig } from '@openmrs/esm-framework';
5
5
  import { type FieldDefinition } from '../../../config-schema';
6
6
  import { useConcept, useConceptAnswers } from '../field.resource';
7
7
  import { ObsField } from './obs-field.component';
8
+ import { PatientRegistrationContext } from '../../patient-registration-context';
8
9
 
9
10
  const mockUseConfig = useConfig as jest.Mock;
10
11
 
@@ -42,6 +43,14 @@ const useConceptMockImpl = (uuid: string) => {
42
43
  ],
43
44
  setMembers: [],
44
45
  };
46
+ } else if (uuid == 'vaccination-date-uuid') {
47
+ data = {
48
+ uuid: 'vaccination-date-uuid',
49
+ display: 'Vaccination Date',
50
+ datatype: { display: 'Date', uuid: 'date' },
51
+ answers: [],
52
+ setMembers: [],
53
+ };
45
54
  } else {
46
55
  throw Error(`Programming error, you probably didn't mean to do this: unknown concept uuid '${uuid}'`);
47
56
  }
@@ -79,12 +88,14 @@ const useConceptAnswersMockImpl = (uuid: string) => {
79
88
  };
80
89
 
81
90
  type FieldProps = {
82
- children: ({ field, form: { touched, errors } }) => React.ReactNode;
91
+ children: ({ field, form: { touched, errors }, meta }) => React.ReactNode;
83
92
  };
84
93
 
85
94
  jest.mock('formik', () => ({
86
95
  ...(jest.requireActual('formik') as object),
87
- Field: jest.fn(({ children }: FieldProps) => <>{children({ field: {}, form: { touched: {}, errors: {} } })}</>),
96
+ Field: jest.fn(({ children }: FieldProps) => (
97
+ <>{children({ field: {}, form: { touched: {}, errors: {} }, meta: { error: undefined } })}</>
98
+ )),
88
99
  useField: jest.fn(() => [{ value: null }, {}]),
89
100
  }));
90
101
 
@@ -118,6 +129,21 @@ const numberFieldDef: FieldDefinition = {
118
129
  customConceptAnswers: [],
119
130
  };
120
131
 
132
+ const dateFieldDef: FieldDefinition = {
133
+ id: 'vac_date',
134
+ type: 'obs',
135
+ label: '',
136
+ placeholder: '',
137
+ showHeading: false,
138
+ uuid: 'vaccination-date-uuid',
139
+ validation: {
140
+ required: false,
141
+ matches: null,
142
+ },
143
+ answerConceptSetUuid: null,
144
+ customConceptAnswers: [],
145
+ };
146
+
121
147
  const codedFieldDef: FieldDefinition = {
122
148
  id: 'nationality',
123
149
  type: 'obs',
@@ -163,6 +189,22 @@ describe('ObsField', () => {
163
189
  expect(screen.getByRole('spinbutton')).toBeInTheDocument();
164
190
  });
165
191
 
192
+ it('renders a datepicker for date concept', async () => {
193
+ render(
194
+ <PatientRegistrationContext.Provider value={{ setFieldValue: jest.fn() }}>
195
+ <ObsField fieldDefinition={dateFieldDef} />
196
+ </PatientRegistrationContext.Provider>,
197
+ );
198
+
199
+ expect(screen.getByRole('textbox')).toBeInTheDocument();
200
+
201
+ const datePickerInput = screen.getByPlaceholderText('dd/mm/yyyy');
202
+ expect(datePickerInput).toBeInTheDocument();
203
+
204
+ await userEvent.type(datePickerInput, '28/05/2024');
205
+ expect(datePickerInput).toHaveValue('28/05/2024');
206
+ });
207
+
166
208
  it('renders a select for a coded concept', () => {
167
209
  render(<ObsField fieldDefinition={codedFieldDef} />);
168
210
  // expect(screen.getByLabelText("Nationality")).toBeInTheDocument();
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { Field } from 'formik';
3
+ import { Layer, Select, SelectItem } from '@carbon/react';
4
+ import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
5
+ import { useTranslation } from 'react-i18next';
6
+ import styles from './../field.scss';
7
+ import classNames from 'classnames';
8
+
9
+ type CustomPersonAttributeFieldProps = {
10
+ id: string;
11
+ personAttributeType: PersonAttributeTypeResponse;
12
+ answerConceptSetUuid: string;
13
+ label?: string;
14
+ customConceptAnswers: Array<{ uuid: string; label?: string }>;
15
+ required: boolean;
16
+ };
17
+
18
+ const CustomPersonAttributeField: React.FC<CustomPersonAttributeFieldProps> = ({
19
+ personAttributeType,
20
+ required,
21
+ id,
22
+ label,
23
+ customConceptAnswers,
24
+ }) => {
25
+ const { t } = useTranslation();
26
+ const fieldName = `attributes.${personAttributeType.uuid}`;
27
+
28
+ return (
29
+ <div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
30
+ <Layer>
31
+ <Field name={fieldName}>
32
+ {({ field, form: { touched, errors }, meta }) => {
33
+ return (
34
+ <>
35
+ <Select
36
+ id={id}
37
+ name={`person-attribute-${personAttributeType.uuid}`}
38
+ labelText={label ?? personAttributeType?.display}
39
+ invalid={errors[fieldName] && touched[fieldName]}
40
+ required={required}
41
+ {...field}>
42
+ <SelectItem value={''} text={t('selectAnOption', 'Select an option')} />
43
+ {customConceptAnswers.map((answer) => (
44
+ <SelectItem key={answer.uuid} value={answer.uuid} text={answer.uuid} />
45
+ ))}
46
+ </Select>
47
+ </>
48
+ );
49
+ }}
50
+ </Field>
51
+ </Layer>
52
+ </div>
53
+ );
54
+ };
55
+
56
+ export default CustomPersonAttributeField;
@@ -6,6 +6,7 @@ import { usePersonAttributeType } from './person-attributes.resource';
6
6
  import { TextPersonAttributeField } from './text-person-attribute-field.component';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import styles from '../field.scss';
9
+ import CustomPersonAttributeField from './custom-person-attribute-field.component';
9
10
 
10
11
  export interface PersonAttributeFieldProps {
11
12
  fieldDefinition: FieldDefinition;
@@ -22,13 +23,26 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
22
23
  switch (personAttributeType.format) {
23
24
  case 'java.lang.String':
24
25
  return (
25
- <TextPersonAttributeField
26
- personAttributeType={personAttributeType}
27
- validationRegex={fieldDefinition.validation?.matches ?? ''}
28
- label={fieldDefinition.label}
29
- required={fieldDefinition.validation?.required ?? false}
30
- id={fieldDefinition?.id}
31
- />
26
+ <>
27
+ {fieldDefinition.renderType === 'select' ? (
28
+ <CustomPersonAttributeField
29
+ personAttributeType={personAttributeType}
30
+ answerConceptSetUuid={fieldDefinition.answerConceptSetUuid}
31
+ label={fieldDefinition.label}
32
+ id={fieldDefinition?.id}
33
+ customConceptAnswers={fieldDefinition.customConceptAnswers ?? []}
34
+ required={fieldDefinition.validation?.required ?? false}
35
+ />
36
+ ) : (
37
+ <TextPersonAttributeField
38
+ personAttributeType={personAttributeType}
39
+ validationRegex={fieldDefinition.validation?.matches ?? ''}
40
+ label={fieldDefinition.label}
41
+ required={fieldDefinition.validation?.required ?? false}
42
+ id={fieldDefinition?.id}
43
+ />
44
+ )}
45
+ </>
32
46
  );
33
47
  case 'org.openmrs.Concept':
34
48
  return (
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  type FetchResponse,
3
+ type Session,
4
+ type StyleguideConfigObject,
5
+ getConfig,
3
6
  openmrsFetch,
4
7
  queueSynchronizationItem,
5
- type Session,
6
8
  restBaseUrl,
7
- getConfig,
8
9
  } from '@openmrs/esm-framework';
9
10
  import { patientRegistration } from '../constants';
10
11
  import {
@@ -131,14 +132,15 @@ export class FormManager {
131
132
 
132
133
  await this.saveObservations(values.obs, savePatientResponse, currentLocation, currentUser, config);
133
134
 
134
- const { patientPhotoUuid } = await getConfig('@openmrs/esm-styleguide');
135
- if (patientPhotoUuid && capturePhotoProps?.imageData) {
135
+ const { patientPhotoConceptUuid } = await getConfig<StyleguideConfigObject>('@openmrs/esm-styleguide');
136
+
137
+ if (patientPhotoConceptUuid && capturePhotoProps?.imageData) {
136
138
  await savePatientPhoto(
137
139
  savePatientResponse.data.uuid,
138
140
  capturePhotoProps.imageData,
139
141
  `${restBaseUrl}/obs`,
140
142
  capturePhotoProps.dateTime || new Date().toISOString(),
141
- patientPhotoUuid,
143
+ patientPhotoConceptUuid,
142
144
  );
143
145
  }
144
146
  }
@@ -195,7 +195,9 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
195
195
  <div>
196
196
  <div className={styles.stickyColumn}>
197
197
  <h4>
198
- {inEditMode ? t('edit', 'Edit') : t('createNew', 'Create New')} {t('patient', 'Patient')}
198
+ {inEditMode
199
+ ? t('editPatientDetails', 'Edit patient details')
200
+ : t('createNewPatient', 'Create new patient')}
199
201
  </h4>
200
202
  {showDummyData && <DummyDataInput setValues={props.setValues} />}
201
203
  <p className={styles.label01}>{t('jumpTo', 'Jump to')}</p>
@@ -231,12 +233,11 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
231
233
  className={styles.spinner}
232
234
  description={`${t('submitting', 'Submitting')} ...`}
233
235
  iconDescription="submitting"
234
- status="active"
235
236
  />
236
237
  ) : inEditMode ? (
237
- t('updatePatient', 'Update Patient')
238
+ t('updatePatient', 'Update patient')
238
239
  ) : (
239
- t('registerPatient', 'Register Patient')
240
+ t('registerPatient', 'Register patient')
240
241
  )}
241
242
  </Button>
242
243
  <Button className={styles.cancelButton} kind="tertiary" onClick={cancelRegistration}>
@@ -5,7 +5,7 @@ export const uuidIdentifier = '05a29f94-c0ed-11e2-94be-8c13b969e334';
5
5
  export const uuidTelephoneNumber = '14d4f066-15f5-102d-96e4-000c29c2a5d7';
6
6
 
7
7
  function dataURItoFile(dataURI: string) {
8
- const byteString = atob(dataURI.split(',')[1]);
8
+ const byteString = window.atob(dataURI.split(',')[1]);
9
9
  const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
10
10
  // write the bytes of the string to a typed array
11
11
  const buffer = new Uint8Array(byteString.length);
@@ -1,5 +1,5 @@
1
- @use '@carbon/styles/scss/spacing';
2
- @use '@carbon/styles/scss/type';
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
3
  @import '~@openmrs/esm-styleguide/src/vars';
4
4
 
5
5
  .title {
@@ -19,23 +19,23 @@
19
19
  }
20
20
 
21
21
  .submitButton {
22
- margin-bottom: spacing.$spacing-05;
22
+ margin-bottom: layout.$spacing-05;
23
23
  width: 11.688rem;
24
24
  display: block;
25
25
  }
26
26
 
27
27
  .infoGrid {
28
28
  width: 100%;
29
- padding-left: spacing.$spacing-07;
29
+ padding-left: layout.$spacing-07;
30
30
  margin-bottom: 40vh;
31
- margin-top: spacing.$spacing-05;
31
+ margin-top: layout.$spacing-05;
32
32
  max-width: 50rem;
33
33
  }
34
34
 
35
35
  .label01 {
36
36
  @include type.type-style('label-01');
37
- margin-top: spacing.$spacing-05;
38
- margin-bottom: spacing.$spacing-05;
37
+ margin-top: layout.$spacing-05;
38
+ margin-bottom: layout.$spacing-05;
39
39
  color: $ui-04;
40
40
  }
41
41
 
@@ -46,7 +46,7 @@
46
46
  }
47
47
 
48
48
  .space05 {
49
- margin: spacing.$spacing-05 0 spacing.$spacing-05 0;
49
+ margin: layout.$spacing-05 0;
50
50
  }
51
51
 
52
52
  .formContainer {
@@ -56,7 +56,7 @@
56
56
 
57
57
  .stickyColumn {
58
58
  position: sticky;
59
- margin-top: spacing.$spacing-05;
59
+ margin-top: layout.$spacing-05;
60
60
  // 3rem for the nav height and 1rem for top margin
61
61
  top: 4rem;
62
62
  }
@@ -68,7 +68,7 @@
68
68
  .linkName {
69
69
  color: $color-gray-70;
70
70
  line-height: 1.38;
71
- font-size: spacing.$spacing-05;
71
+ font-size: layout.$spacing-05;
72
72
  font-weight: 600;
73
73
 
74
74
  &:active {
@@ -94,8 +94,11 @@
94
94
  }
95
95
 
96
96
  .spinner {
97
- &:global(.cds--inline-loading) {
98
- min-height: 1rem;
97
+ min-height: 1rem;
98
+ width: max-content;
99
+
100
+ :global(.cds--inline-loading__text) {
101
+ @include type.type-style('body-01');
99
102
  }
100
103
  }
101
104
 
@@ -109,6 +112,6 @@ html[dir='rtl'] {
109
112
 
110
113
  .infoGrid {
111
114
  padding-left: unset;
112
- padding-right: spacing.$spacing-07;
115
+ padding-right: layout.$spacing-07;
113
116
  }
114
117
  }
@@ -264,7 +264,7 @@ describe('Registering a new patient', () => {
264
264
  });
265
265
 
266
266
  await fillRequiredFields();
267
- await user.click(await screen.findByText('Register Patient'));
267
+ await user.click(await screen.findByText(/Register Patient/i));
268
268
  expect(mockedSavePatient).toHaveBeenCalledWith(
269
269
  expect.objectContaining({
270
270
  identifiers: [], //TODO when the identifer story is finished: { identifier: '', identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334', location: '' },
@@ -293,7 +293,7 @@ describe('Registering a new patient', () => {
293
293
  const givenNameInput = (await screen.findByLabelText('First Name')) as HTMLInputElement;
294
294
 
295
295
  await user.type(givenNameInput, '5');
296
- await user.click(screen.getByText('Register Patient'));
296
+ await user.click(screen.getByText(/Register Patient/i));
297
297
 
298
298
  expect(mockedSavePatientForm).not.toHaveBeenCalled();
299
299
  });
@@ -317,7 +317,7 @@ describe('Registering a new patient', () => {
317
317
  const nationality = within(customSection).getByLabelText('Nationality');
318
318
  await user.selectOptions(nationality, 'USA');
319
319
 
320
- await user.click(screen.getByText('Register Patient'));
320
+ await user.click(screen.getByText(/Register Patient/i));
321
321
 
322
322
  expect(mockedSavePatient).toHaveBeenCalled();
323
323
 
@@ -350,7 +350,7 @@ describe('Registering a new patient', () => {
350
350
 
351
351
  mockedSaveEncounter.mockRejectedValue({ status: 400, responseBody: { error: { message: 'an error message' } } });
352
352
 
353
- const registerPatientButton = screen.getByText('Register Patient');
353
+ const registerPatientButton = screen.getByText(/Register Patient/i);
354
354
 
355
355
  await user.click(registerPatientButton);
356
356
 
@@ -405,7 +405,7 @@ describe('Updating an existing patient record', () => {
405
405
  expect(givenNameInput.value).toBe('John');
406
406
  expect(familyNameInput.value).toBe('Wilson');
407
407
  expect(middleNameInput.value).toBeFalsy();
408
- expect(dateOfBirthInput.value).toBe('4/4/1972');
408
+ expect(dateOfBirthInput.value).toBe('04/04/1972');
409
409
  expect(genderInput.value).toBe('male');
410
410
 
411
411
  // do some edits
@@ -415,7 +415,7 @@ describe('Updating an existing patient record', () => {
415
415
  await user.type(givenNameInput, 'Eric');
416
416
  await user.type(middleNameInput, 'Johnson');
417
417
  await user.type(familyNameInput, 'Smith');
418
- await user.click(screen.getByText('Update Patient'));
418
+ await user.click(screen.getByText(/Update patient/i));
419
419
 
420
420
  expect(mockedSavePatient).toHaveBeenCalledWith(
421
421
  false,