@kenyaemr/esm-patient-registration-app 8.1.1-pre.118 → 8.1.1-pre.119

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 (29) hide show
  1. package/.turbo/turbo-build.log +13 -13
  2. package/dist/10.js +1 -0
  3. package/dist/10.js.map +1 -0
  4. package/dist/130.js +1 -1
  5. package/dist/130.js.map +1 -1
  6. package/dist/574.js +1 -1
  7. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  8. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +37 -37
  9. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  10. package/dist/main.js +1 -1
  11. package/dist/main.js.map +1 -1
  12. package/dist/routes.json +1 -1
  13. package/package.json +1 -1
  14. package/src/client-registry/hie-client-registry/hie-resource.ts +5 -12
  15. package/src/config-schema.ts +7 -0
  16. package/src/patient-registration/field/field.scss +17 -0
  17. package/src/patient-registration/field/id/id-field.component.tsx +8 -6
  18. package/src/patient-registration/field/id/id-field.test.tsx +27 -8
  19. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +105 -0
  20. package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +48 -0
  21. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +12 -1
  22. package/src/patient-registration/form-manager.test.ts +18 -0
  23. package/src/patient-registration/form-manager.ts +10 -5
  24. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +17 -9
  25. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +106 -58
  26. package/src/patient-registration/input/input.scss +5 -0
  27. package/translations/en.json +2 -0
  28. package/dist/471.js +0 -1
  29. package/dist/471.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":"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}],"modals":[{"name":"cancel-patient-edit-modal","component":"cancelPatientEditModal"},{"name":"delete-identifier-confirmation-modal","component":"deleteIdentifierConfirmationModal"},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal"},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal"},{"component":"hieConfirmationModal","name":"hie-confirmation-modal"}],"version":"8.1.1-pre.118"}
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":"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}],"modals":[{"name":"cancel-patient-edit-modal","component":"cancelPatientEditModal"},{"name":"delete-identifier-confirmation-modal","component":"deleteIdentifierConfirmationModal"},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal"},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal"},{"component":"hieConfirmationModal","name":"hie-confirmation-modal"}],"version":"8.1.1-pre.119"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-patient-registration-app",
3
- "version": "8.1.1-pre.118",
3
+ "version": "8.1.1-pre.119",
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",
@@ -1,8 +1,7 @@
1
- import { add, capitalize } from 'lodash-es';
1
+ import capitalize from 'lodash-es/capitalize';
2
2
  import { type PatientIdentifierValue, type FormValues } from '../../patient-registration/patient-registration.types';
3
3
  import { type MapperConfig, type HIEPatient, type ErrorResponse } from './hie-types';
4
- import { getConfig } from '@openmrs/esm-framework';
5
- import { type RegistrationConfig } from '../../config-schema';
4
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
6
5
  import { v4 } from 'uuid';
7
6
  /**
8
7
  * Represents a client for interacting with a Health Information Exchange (HIE) resource.
@@ -10,16 +9,10 @@ import { v4 } from 'uuid';
10
9
  */
11
10
  class HealthInformationExchangeClient<T> {
12
11
  async fetchResource(resourceType: string, params: Record<string, string>): Promise<T> {
13
- const {
14
- hieClientRegistry: { baseUrl, encodedCredentials },
15
- } = await getConfig<RegistrationConfig>('@kenyaemr/esm-patient-registration-app');
16
- const urlParams = new URLSearchParams(params);
17
- const response = await fetch(`${baseUrl}${resourceType}?${urlParams}`, {
18
- headers: new Headers({
19
- Authorization: `Basic ${encodedCredentials}`,
20
- }),
21
- });
12
+ const [identifierType, identifierValue] = Object.entries(params)[0];
13
+ const url = `${restBaseUrl}/kenyaemr/getSHAPatient/${identifierValue}/${identifierType}`;
22
14
 
15
+ const response = await openmrsFetch(url);
23
16
  return response.json();
24
17
  }
25
18
  }
@@ -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?: {
@@ -193,6 +194,12 @@ export const esmPatientRegistrationSchema = {
193
194
  _description: 'Optional RegEx for testing the validity of the input.',
194
195
  },
195
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
+ },
196
203
  answerConceptSetUuid: {
197
204
  _type: Type.ConceptUuid,
198
205
  _default: null,
@@ -99,7 +99,13 @@
99
99
  align-items: center;
100
100
  }
101
101
 
102
+ .arrowRightIcon {
103
+ fill: currentColor !important;
104
+ }
105
+
102
106
  .configureIdentifiersButton {
107
+ display: flex;
108
+ align-items: center;
103
109
  margin: 0 0 layout.$spacing-05 layout.$spacing-05;
104
110
 
105
111
  svg {
@@ -115,6 +121,17 @@
115
121
  margin-bottom: layout.$spacing-05;
116
122
  }
117
123
 
124
+ .locationAttributeFieldContainer {
125
+ position: relative;
126
+
127
+ .loadingContainer {
128
+ background-color: colors.$white;
129
+ position: absolute;
130
+ right: layout.$spacing-07;
131
+ bottom: layout.$spacing-02;
132
+ }
133
+ }
134
+
118
135
  :global(.omrs-breakpoint-lt-desktop) {
119
136
  .grid {
120
137
  grid-template-columns: 1fr;
@@ -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>
@@ -1,18 +1,19 @@
1
1
  import React from 'react';
2
- import userEvent from '@testing-library/user-event';
3
- import { render, screen } from '@testing-library/react';
4
2
  import { Form, Formik } from 'formik';
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
5
  import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
6
- import { Identifiers } from './id-field.component';
7
- import { mockOpenmrsId, mockIdentifierTypes, mockPatient, mockSession } from '__mocks__';
8
- import { type RegistrationConfig, esmPatientRegistrationSchema } from '../../../config-schema';
9
- import { type Resources, ResourcesContext } from '../../../offline.resources';
6
+ import { type AddressTemplate, type IdentifierSource } from '../../patient-registration.types';
7
+ import { mockIdentifierTypes, mockOpenmrsId, mockPatient, mockSession } from '__mocks__';
8
+ import { esmPatientRegistrationSchema, type RegistrationConfig } from '../../../config-schema';
9
+ import { ResourcesContext, type Resources } from '../../../offline.resources';
10
10
  import { PatientRegistrationContext, type PatientRegistrationContextProps } from '../../patient-registration-context';
11
+ import { Identifiers, setIdentifierSource } from './id-field.component';
11
12
 
12
13
  const mockUseConfig = jest.mocked(useConfig<RegistrationConfig>);
13
14
 
14
15
  const mockResourcesContextValue = {
15
- addressTemplate: null,
16
+ addressTemplate: null as unknown as AddressTemplate,
16
17
  currentSession: mockSession.data,
17
18
  identifierTypes: [],
18
19
  relationshipTypes: [],
@@ -52,7 +53,7 @@ const mockContextValues: PatientRegistrationContextProps = {
52
53
  setInitialFormValues: jest.fn(),
53
54
  validationSchema: null,
54
55
  values: mockInitialFormValues,
55
- };
56
+ } as unknown as PatientRegistrationContextProps;
56
57
 
57
58
  describe('Identifiers', () => {
58
59
  beforeEach(() => {
@@ -120,3 +121,21 @@ describe('Identifiers', () => {
120
121
  expect(screen.getByRole('button', { name: 'Close overlay' })).toBeInTheDocument();
121
122
  });
122
123
  });
124
+
125
+ describe('setIdentifierSource', () => {
126
+ describe('auto-generation', () => {
127
+ it('should return auto-generated as the identifier value', () => {
128
+ const identifierSource = { autoGenerationOption: { automaticGenerationEnabled: true } } as IdentifierSource;
129
+ const { identifierValue } = setIdentifierSource(identifierSource, '', '');
130
+ expect(identifierValue).toBe('auto-generated');
131
+ });
132
+
133
+ it('should return the identifier value when manual entry enabled', () => {
134
+ const identifierSource = {
135
+ autoGenerationOption: { automaticGenerationEnabled: true, manualEntryEnabled: true },
136
+ } as IdentifierSource;
137
+ const { identifierValue } = setIdentifierSource(identifierSource, '10001V', '');
138
+ expect(identifierValue).toBe('10001V');
139
+ });
140
+ });
141
+ });
@@ -0,0 +1,105 @@
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import { Field, useField } from 'formik';
4
+ import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
5
+ import styles from './../field.scss';
6
+ import { useLocations } from './location-person-attribute-field.resource';
7
+ import { ComboBox, InlineLoading, Layer } from '@carbon/react';
8
+ import { useTranslation } from 'react-i18next';
9
+
10
+ export interface LocationPersonAttributeFieldProps {
11
+ id: string;
12
+ personAttributeType: PersonAttributeTypeResponse;
13
+ label?: string;
14
+ locationTag: string;
15
+ required?: boolean;
16
+ }
17
+
18
+ export function LocationPersonAttributeField({
19
+ personAttributeType,
20
+ id,
21
+ label,
22
+ locationTag,
23
+ required,
24
+ }: LocationPersonAttributeFieldProps) {
25
+ const { t } = useTranslation();
26
+ const fieldName = `attributes.${personAttributeType.uuid}`;
27
+ const [field, meta, { setValue }] = useField(`attributes.${personAttributeType.uuid}`);
28
+ const [searchQuery, setSearchQuery] = useState<string>('');
29
+ const { locations, isLoading, loadingNewData } = useLocations(locationTag || null, searchQuery);
30
+ const prevLocationOptions = useRef([]);
31
+
32
+ const locationOptions = useMemo(() => {
33
+ if (!(isLoading && loadingNewData)) {
34
+ const newOptions = locations.map(({ resource: { id, name } }) => ({ value: id, label: name }));
35
+ prevLocationOptions.current = newOptions;
36
+ return newOptions;
37
+ }
38
+ return prevLocationOptions.current;
39
+ }, [locations, isLoading, loadingNewData]);
40
+
41
+ const selectedItem = useMemo(() => {
42
+ if (typeof meta.value === 'string') {
43
+ return locationOptions.find(({ value }) => value === meta.value) || null;
44
+ }
45
+ if (typeof meta.value === 'object' && meta.value) {
46
+ return locationOptions.find(({ value }) => value === meta.value.uuid) || null;
47
+ }
48
+ return null;
49
+ }, [locationOptions, meta.value]);
50
+
51
+ // Callback for when updating the combobox input
52
+ const handleInputChange = useCallback(
53
+ (value: string | null) => {
54
+ if (value) {
55
+ // If the value exists in the locationOptions (i.e. a label matches the input), exit the function
56
+ if (locationOptions.find(({ label }) => label === value)) return;
57
+ // If the input is a new value, set the search query
58
+ setSearchQuery(value);
59
+ // Clear the current selected value since the input doesn't match any existing options
60
+ setValue(null);
61
+ }
62
+ },
63
+ [locationOptions, setValue],
64
+ );
65
+ const handleSelect = useCallback(
66
+ ({ selectedItem }) => {
67
+ if (selectedItem) {
68
+ setValue(selectedItem.value);
69
+ }
70
+ },
71
+ [setValue],
72
+ );
73
+
74
+ return (
75
+ <div
76
+ className={classNames(styles.customField, styles.halfWidthInDesktopView, styles.locationAttributeFieldContainer)}>
77
+ <Layer>
78
+ <Field name={fieldName}>
79
+ {({ field, form: { touched, errors } }) => {
80
+ return (
81
+ <ComboBox
82
+ id={id}
83
+ name={`person-attribute-${personAttributeType.uuid}`}
84
+ titleText={label}
85
+ items={locationOptions}
86
+ placeholder={t('searchLocationPersonAttribute', 'Search location')}
87
+ onInputChange={handleInputChange}
88
+ required={required}
89
+ onChange={handleSelect}
90
+ selectedItem={selectedItem}
91
+ invalid={errors[fieldName] && touched[fieldName]}
92
+ typeahead
93
+ />
94
+ );
95
+ }}
96
+ </Field>
97
+ </Layer>
98
+ {loadingNewData && (
99
+ <div className={styles.loadingContainer}>
100
+ <InlineLoading />
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ }
@@ -0,0 +1,48 @@
1
+ import { useMemo } from 'react';
2
+ import { type FetchResponse, fhirBaseUrl, openmrsFetch, useDebounce } from '@openmrs/esm-framework';
3
+ import { type LocationEntry, type LocationResponse } from '@kenyaemr/esm-service-queues-app/src/types';
4
+ import useSWR from 'swr';
5
+
6
+ interface UseLocationsResult {
7
+ locations: Array<LocationEntry>;
8
+ isLoading: boolean;
9
+ loadingNewData: boolean;
10
+ }
11
+
12
+ export function useLocations(locationTag: string | null, searchQuery: string = ''): UseLocationsResult {
13
+ const debouncedSearchQuery = useDebounce(searchQuery);
14
+
15
+ const constructUrl = useMemo(() => {
16
+ let url = `${fhirBaseUrl}/Location?`;
17
+ let urlSearchParameters = new URLSearchParams();
18
+ urlSearchParameters.append('_summary', 'data');
19
+
20
+ if (!debouncedSearchQuery) {
21
+ urlSearchParameters.append('_count', '10');
22
+ }
23
+
24
+ if (locationTag) {
25
+ urlSearchParameters.append('_tag', locationTag);
26
+ }
27
+
28
+ if (typeof debouncedSearchQuery === 'string' && debouncedSearchQuery != '') {
29
+ urlSearchParameters.append('name:contains', debouncedSearchQuery);
30
+ }
31
+
32
+ return url + urlSearchParameters.toString();
33
+ }, [locationTag, debouncedSearchQuery]);
34
+
35
+ const { data, error, isLoading, isValidating } = useSWR<FetchResponse<LocationResponse>, Error>(
36
+ constructUrl,
37
+ openmrsFetch,
38
+ );
39
+
40
+ return useMemo(
41
+ () => ({
42
+ locations: data?.data?.entry || [],
43
+ isLoading,
44
+ loadingNewData: isValidating,
45
+ }),
46
+ [data, isLoading, isValidating],
47
+ );
48
+ }
@@ -1,11 +1,12 @@
1
1
  import React, { useMemo } from 'react';
2
- import { InlineNotification, TextInputSkeleton, SkeletonText } from '@carbon/react';
2
+ import { InlineNotification, TextInputSkeleton } from '@carbon/react';
3
3
  import { type FieldDefinition } from '../../../config-schema';
4
4
  import { CodedPersonAttributeField } from './coded-person-attribute-field.component';
5
5
  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 { LocationPersonAttributeField } from './location-person-attribute-field.component';
9
10
  import CustomPersonAttributeField from './custom-person-attribute-field.component';
10
11
 
11
12
  export interface PersonAttributeFieldProps {
@@ -55,6 +56,16 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
55
56
  required={fieldDefinition.validation?.required ?? false}
56
57
  />
57
58
  );
59
+ case 'org.openmrs.Location':
60
+ return (
61
+ <LocationPersonAttributeField
62
+ personAttributeType={personAttributeType}
63
+ locationTag={fieldDefinition.locationTag}
64
+ label={fieldDefinition.label}
65
+ id={fieldDefinition?.id}
66
+ required={fieldDefinition.validation?.required ?? false}
67
+ />
68
+ );
58
69
  default:
59
70
  return (
60
71
  <InlineNotification kind="error" title="Error">
@@ -1,8 +1,11 @@
1
1
  import { FormManager } from './form-manager';
2
2
  import { type FormValues } from './patient-registration.types';
3
+ import { generateIdentifier } from './patient-registration.resource';
3
4
 
4
5
  jest.mock('./patient-registration.resource');
5
6
 
7
+ const mockGenerateIdentifier = generateIdentifier as jest.Mock;
8
+
6
9
  const formValues: FormValues = {
7
10
  patientUuid: '',
8
11
  givenName: '',
@@ -66,5 +69,20 @@ describe('FormManager', () => {
66
69
  },
67
70
  ]);
68
71
  });
72
+
73
+ it('should generate identifier if it has autoGeneration and manual entry disabled', async () => {
74
+ formValues.identifiers.foo.autoGeneration = true;
75
+ formValues.identifiers.foo.selectedSource.autoGenerationOption.manualEntryEnabled = false;
76
+ mockGenerateIdentifier.mockResolvedValue({ data: { identifier: '10001V' } });
77
+ await FormManager.savePatientIdentifiers(true, undefined, formValues.identifiers, {}, 'Nyc');
78
+ expect(mockGenerateIdentifier.mock.calls).toHaveLength(1);
79
+ });
80
+
81
+ it('should not generate identifiers if manual entry enabled and identifier value given', async () => {
82
+ formValues.identifiers.foo.autoGeneration = true;
83
+ formValues.identifiers.foo.selectedSource.autoGenerationOption.manualEntryEnabled = true;
84
+ await FormManager.savePatientIdentifiers(true, undefined, formValues.identifiers, {}, 'Nyc');
85
+ expect(mockGenerateIdentifier.mock.calls).toHaveLength(0);
86
+ });
69
87
  });
70
88
  });
@@ -240,11 +240,16 @@ export class FormManager {
240
240
  initialValue,
241
241
  } = patientIdentifier;
242
242
 
243
- const identifier = !autoGeneration
244
- ? identifierValue
245
- : await (
246
- await generateIdentifier(selectedSource.uuid)
247
- ).data.identifier;
243
+ const autoGenerationManualEntry =
244
+ autoGeneration && selectedSource?.autoGenerationOption?.manualEntryEnabled && !!identifierValue;
245
+
246
+ const identifier =
247
+ !autoGeneration || autoGenerationManualEntry
248
+ ? identifierValue
249
+ : await (
250
+ await generateIdentifier(selectedSource.uuid)
251
+ ).data.identifier;
252
+
248
253
  const identifierToCreate = {
249
254
  uuid: identifierUuid,
250
255
  identifier,
@@ -26,7 +26,8 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
26
26
  () => identifierTypes.find((identifierType) => identifierType.uuid === patientIdentifier.identifierTypeUuid),
27
27
  [patientIdentifier, identifierTypes],
28
28
  );
29
- const { autoGeneration, initialValue, identifierValue, identifierName, required } = patientIdentifier;
29
+ const { autoGeneration, initialValue, identifierValue, identifierName, required, selectedSource } = patientIdentifier;
30
+ const manualEntryEnabled = selectedSource?.autoGenerationOption?.manualEntryEnabled;
30
31
  const [hideInputField, setHideInputField] = useState(autoGeneration || initialValue === identifierValue);
31
32
  const name = `identifiers.${fieldName}.identifierValue`;
32
33
  const [identifierField, identifierFieldMeta] = useField(name);
@@ -46,8 +47,8 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
46
47
  setFieldValue(`identifiers.${fieldName}`, {
47
48
  ...patientIdentifier,
48
49
  identifierValue: initialValue,
49
- selectedSource: null,
50
- autoGeneration: false,
50
+ selectedSource,
51
+ autoGeneration,
51
52
  } as PatientIdentifierValue);
52
53
  // eslint-disable-next-line react-hooks/exhaustive-deps
53
54
  }, [initialValue, setHideInputField]);
@@ -57,6 +58,7 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
57
58
  setFieldValue(`identifiers.${fieldName}`, {
58
59
  ...patientIdentifier,
59
60
  ...setIdentifierSource(identifierType?.identifierSources?.[0], initialValue, initialValue),
61
+ ...(autoGeneration && manualEntryEnabled && { identifierValue: initialValue ?? '' }),
60
62
  });
61
63
  };
62
64
 
@@ -83,9 +85,12 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
83
85
  }
84
86
  };
85
87
 
88
+ const showEditButton = !required && hideInputField && (!!initialValue || manualEntryEnabled);
89
+ const showResetButton =
90
+ (!!initialValue && initialValue !== identifierValue) || (!hideInputField && manualEntryEnabled);
86
91
  return (
87
92
  <div className={styles.IDInput}>
88
- {!autoGeneration && !hideInputField ? (
93
+ {!hideInputField ? (
89
94
  <Input
90
95
  id={name}
91
96
  labelText={identifierName}
@@ -99,8 +104,10 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
99
104
  />
100
105
  ) : (
101
106
  <div className={styles.textID}>
102
- <p className={styles.label}>{identifierName}</p>
103
- <p data-testid="identifier-label" className={styles.bodyShort02}>
107
+ <p data-testid="identifier-label" className={styles.label}>
108
+ {required ? identifierName : `${t('optionalIdentifierLabel', { identifierName })}`}
109
+ </p>
110
+ <p data-testid="identifier-placeholder" className={styles.bodyShort02}>
104
111
  {autoGeneration ? t('autoGeneratedPlaceholderText', 'Auto-generated') : identifierValue}
105
112
  </p>
106
113
  <input data-testid="identifier-input" type="hidden" {...identifierField} disabled />
@@ -110,10 +117,11 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
110
117
  )}
111
118
  </div>
112
119
  )}
113
- <div style={{ marginBottom: '1rem' }}>
114
- {!patientIdentifier.required && patientIdentifier.initialValue && hideInputField && (
120
+ <div className={styles.actionButtonContainer}>
121
+ {showEditButton && (
115
122
  <UserHasAccess privilege="Edit Patient Identifiers">
116
123
  <Button
124
+ data-testid="edit-button"
117
125
  size="md"
118
126
  kind="ghost"
119
127
  onClick={handleEdit}
@@ -124,7 +132,7 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
124
132
  </Button>
125
133
  </UserHasAccess>
126
134
  )}
127
- {initialValue && initialValue !== identifierValue && (
135
+ {showResetButton && (
128
136
  <UserHasAccess privilege="Edit Patient Identifiers">
129
137
  <Button
130
138
  size="md"