@kenyaemr/esm-patient-registration-app 4.6.0 → 5.2.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.
- package/dist/130.js +1 -1
- package/dist/130.js.LICENSE.txt +0 -2
- package/dist/130.js.map +1 -1
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/255.js +2 -0
- package/dist/255.js.map +1 -0
- package/dist/303.js +1 -0
- package/dist/303.js.map +1 -0
- package/dist/319.js +1 -1
- package/dist/330.js +1 -1
- package/dist/388.js +2 -0
- package/dist/388.js.LICENSE.txt +34 -0
- package/dist/388.js.map +1 -0
- package/dist/460.js +1 -1
- package/dist/537.js +1 -1
- package/dist/537.js.map +1 -1
- package/dist/574.js +1 -1
- package/dist/59.js +1 -0
- package/dist/59.js.map +1 -0
- package/dist/598.js +1 -0
- package/dist/598.js.map +1 -0
- package/dist/62.js +1 -1
- package/dist/62.js.map +1 -1
- package/dist/729.js +1 -0
- package/dist/729.js.map +1 -0
- package/dist/735.js +1 -1
- package/dist/757.js +1 -1
- package/dist/788.js +1 -1
- package/dist/807.js +1 -1
- package/dist/833.js +1 -1
- package/dist/840.js +1 -0
- package/dist/840.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +35 -0
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-registration-app.js +1 -1
- package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +114 -221
- package/dist/openmrs-esm-patient-registration-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +4 -3
- package/src/declarations.d.ts +2 -0
- package/src/index.ts +23 -14
- package/src/patient-registration/field/address/address-field.component.tsx +2 -2
- package/src/patient-registration/field/address/custom-address-field.component.tsx +3 -2
- package/src/patient-registration/field/dob/dob.component.tsx +51 -42
- package/src/patient-registration/field/dob/dob.test.tsx +1 -14
- package/src/patient-registration/field/field.resource.ts +5 -5
- package/src/patient-registration/field/id/id-field.component.tsx +8 -6
- package/src/patient-registration/field/id/{identifier-selection-overlay.tsx → identifier-selection-overlay.component.tsx} +27 -23
- package/src/patient-registration/field/obs/obs-field.component.tsx +4 -3
- package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +2 -1
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +4 -3
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +8 -2
- package/src/patient-registration/field/person-attributes/person-attributes.resource.tsx +1 -1
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +3 -2
- package/src/patient-registration/input/basic-input/input/input.component.tsx +122 -2
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +7 -6
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +109 -6
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.scss +9 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +2 -2
- package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +2 -1
- package/src/patient-registration/patient-registration.component.tsx +49 -44
- package/src/patient-registration/patient-registration.resource.tsx +18 -7
- package/src/patient-registration/patient-registration.scss +6 -0
- package/src/patient-registration/patient-registration.test.tsx +19 -21
- package/src/patient-registration/section/death-info/death-info-section.component.tsx +2 -1
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +1 -14
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +27 -20
- package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +1 -1
- package/src/patient-registration/validation/patient-registration-validation.tsx +6 -0
- package/src/root.component.tsx +3 -2
- package/src/routes.json +7 -0
- package/translations/am.json +18 -7
- package/translations/ar.json +17 -6
- package/translations/en.json +16 -5
- package/translations/es.json +17 -6
- package/translations/fr.json +18 -7
- package/translations/he.json +28 -17
- package/translations/km.json +30 -19
- package/dist/117.js +0 -2
- package/dist/117.js.map +0 -1
- package/dist/218.js +0 -1
- package/dist/218.js.map +0 -1
- package/dist/275.js +0 -1
- package/dist/275.js.map +0 -1
- package/dist/317.js +0 -2
- package/dist/317.js.LICENSE.txt +0 -6
- package/dist/317.js.map +0 -1
- package/dist/348.js +0 -1
- package/dist/348.js.map +0 -1
- package/dist/520.js +0 -2
- package/dist/520.js.LICENSE.txt +0 -14
- package/dist/520.js.map +0 -1
- package/dist/635.js +0 -1
- package/dist/635.js.map +0 -1
- package/dist/68.js +0 -1
- package/dist/68.js.map +0 -1
- package/dist/693.js +0 -1
- package/dist/693.js.map +0 -1
- package/dist/742.js +0 -1
- package/dist/742.js.map +0 -1
- package/dist/857.js +0 -1
- package/dist/857.js.map +0 -1
- package/dist/975.js +0 -1
- package/dist/975.js.map +0 -1
- /package/dist/{117.js.LICENSE.txt → 255.js.LICENSE.txt} +0 -0
- /package/src/patient-registration/ui-components/overlay/{index.tsx → overlay.component.tsx} +0 -0
package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx
CHANGED
|
@@ -42,7 +42,13 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
|
|
|
42
42
|
default:
|
|
43
43
|
return (
|
|
44
44
|
<InlineNotification kind="error" title="Error">
|
|
45
|
-
|
|
45
|
+
{t(
|
|
46
|
+
'unknownPatientAttributeType',
|
|
47
|
+
'Patient attribute type has unknown format {{personAttributeTypeFormat}}',
|
|
48
|
+
{
|
|
49
|
+
personAttributeTypeFormat: personAttributeType.format,
|
|
50
|
+
},
|
|
51
|
+
)}
|
|
46
52
|
</InlineNotification>
|
|
47
53
|
);
|
|
48
54
|
}
|
|
@@ -62,7 +68,7 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
|
|
|
62
68
|
<div>
|
|
63
69
|
{fieldDefinition.showHeading && <h4 className={styles.productiveHeading02Light}>{fieldDefinition?.label}</h4>}
|
|
64
70
|
<InlineNotification kind="error" title={t('error', 'Error')}>
|
|
65
|
-
{t('unableToFetch', 'Unable to fetch person attribute type - {personattributetype}', {
|
|
71
|
+
{t('unableToFetch', 'Unable to fetch person attribute type - {{personattributetype}}', {
|
|
66
72
|
personattributetype: fieldDefinition?.label ?? fieldDefinition?.id,
|
|
67
73
|
})}
|
|
68
74
|
</InlineNotification>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FetchResponse, openmrsFetch
|
|
1
|
+
import { FetchResponse, openmrsFetch } from '@openmrs/esm-framework';
|
|
2
2
|
import useSWRImmutable from 'swr/immutable';
|
|
3
3
|
import { PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
4
4
|
|
package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
2
3
|
import { Field } from 'formik';
|
|
3
4
|
import { useTranslation } from 'react-i18next';
|
|
4
5
|
import { Input } from '../../input/basic-input/input/input.component';
|
|
5
|
-
import styles from './../field.scss';
|
|
6
6
|
import { PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
7
|
+
import styles from './../field.scss';
|
|
7
8
|
|
|
8
9
|
export interface TextPersonAttributeFieldProps {
|
|
9
10
|
id: string;
|
|
@@ -37,7 +38,7 @@ export function TextPersonAttributeField({
|
|
|
37
38
|
const fieldName = `attributes.${personAttributeType.uuid}`;
|
|
38
39
|
|
|
39
40
|
return (
|
|
40
|
-
<div className={
|
|
41
|
+
<div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
|
|
41
42
|
<Field name={fieldName} validate={validateInput}>
|
|
42
43
|
{({ field, form: { touched, errors }, meta }) => {
|
|
43
44
|
return (
|
|
@@ -1,13 +1,133 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { Layer, TextInput
|
|
3
|
+
import { Layer, TextInput } from '@carbon/react';
|
|
4
4
|
import { useField } from 'formik';
|
|
5
5
|
|
|
6
|
+
// FIXME Temporarily imported here
|
|
7
|
+
export interface TextInputProps
|
|
8
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'defaultValue' | 'id' | 'size' | 'value'> {
|
|
9
|
+
/**
|
|
10
|
+
* Specify an optional className to be applied to the `<input>` node
|
|
11
|
+
*/
|
|
12
|
+
className?: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optionally provide the default value of the `<input>`
|
|
16
|
+
*/
|
|
17
|
+
defaultValue?: string | number;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Specify whether the `<input>` should be disabled
|
|
21
|
+
*/
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Specify whether to display the character counter
|
|
26
|
+
*/
|
|
27
|
+
enableCounter?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Provide text that is used alongside the control label for additional help
|
|
31
|
+
*/
|
|
32
|
+
helperText?: React.ReactNode;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Specify whether you want the underlying label to be visually hidden
|
|
36
|
+
*/
|
|
37
|
+
hideLabel?: boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Specify a custom `id` for the `<input>`
|
|
41
|
+
*/
|
|
42
|
+
id: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `true` to use the inline version.
|
|
46
|
+
*/
|
|
47
|
+
inline?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Specify whether the control is currently invalid
|
|
51
|
+
*/
|
|
52
|
+
invalid?: boolean;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Provide the text that is displayed when the control is in an invalid state
|
|
56
|
+
*/
|
|
57
|
+
invalidText?: React.ReactNode;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Provide the text that will be read by a screen reader when visiting this
|
|
61
|
+
* control
|
|
62
|
+
*/
|
|
63
|
+
labelText: React.ReactNode;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `true` to use the light version. For use on $ui-01 backgrounds only.
|
|
67
|
+
* Don't use this to make tile background color same as container background color.
|
|
68
|
+
* 'The `light` prop for `TextInput` has ' +
|
|
69
|
+
'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'
|
|
70
|
+
*/
|
|
71
|
+
light?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Max character count allowed for the input. This is needed in order for enableCounter to display
|
|
75
|
+
*/
|
|
76
|
+
maxCount?: number;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Optionally provide an `onChange` handler that is called whenever `<input>`
|
|
80
|
+
* is updated
|
|
81
|
+
*/
|
|
82
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Optionally provide an `onClick` handler that is called whenever the
|
|
86
|
+
* `<input>` is clicked
|
|
87
|
+
*/
|
|
88
|
+
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Specify the placeholder attribute for the `<input>`
|
|
92
|
+
*/
|
|
93
|
+
placeholder?: string;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Whether the input should be read-only
|
|
97
|
+
*/
|
|
98
|
+
readOnly?: boolean;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Specify the size of the Text Input. Currently supports the following:
|
|
102
|
+
*/
|
|
103
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Specify the type of the `<input>`
|
|
107
|
+
*/
|
|
108
|
+
type?: string;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Specify the value of the `<input>`
|
|
112
|
+
*/
|
|
113
|
+
value?: string | number | undefined;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Specify whether the control is currently in warning state
|
|
117
|
+
*/
|
|
118
|
+
warn?: boolean;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Provide the text that is displayed when the control is in warning state
|
|
122
|
+
*/
|
|
123
|
+
warnText?: React.ReactNode;
|
|
124
|
+
}
|
|
125
|
+
|
|
6
126
|
interface InputProps extends TextInputProps {
|
|
7
127
|
checkWarning?(value: string): string;
|
|
8
128
|
}
|
|
9
129
|
|
|
10
|
-
export const Input: React.FC<
|
|
130
|
+
export const Input: React.FC<InputProps> = ({ checkWarning, ...props }) => {
|
|
11
131
|
const [field, meta] = useField(props.name);
|
|
12
132
|
const { t } = useTranslation();
|
|
13
133
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
2
3
|
import { TextInput, Layer } from '@carbon/react';
|
|
3
4
|
import SelectionTick from './selection-tick.component';
|
|
4
5
|
import styles from '../input.scss';
|
|
@@ -98,19 +99,19 @@ const ComboInput: React.FC<ComboInputProps> = ({ entries, fieldProps, handleInpu
|
|
|
98
99
|
<div id="downshift-1-menu" className="cds--list-box__menu" role="listbox">
|
|
99
100
|
{filteredEntries.map((entry, indx) => (
|
|
100
101
|
<div
|
|
102
|
+
className={classNames('cds--list-box__menu-item', {
|
|
103
|
+
'cds--list-box__menu-item--highlighted': indx === highlightedEntry,
|
|
104
|
+
})}
|
|
101
105
|
key={indx}
|
|
102
106
|
id="downshift-1-item-0"
|
|
103
107
|
role="option"
|
|
104
|
-
className={`cds--list-box__menu-item ${
|
|
105
|
-
indx === highlightedEntry && 'cds--list-box__menu-item--highlighted'
|
|
106
|
-
}`}
|
|
107
108
|
tabIndex={-1}
|
|
108
109
|
aria-selected="true"
|
|
109
110
|
onClick={() => handleOptionClick(entry)}>
|
|
110
111
|
<div
|
|
111
|
-
className={
|
|
112
|
-
|
|
113
|
-
}
|
|
112
|
+
className={classNames('cds--list-box__menu-item__option', styles.comboInputItemOption, {
|
|
113
|
+
'cds--list-box__menu-item--active': entry === value,
|
|
114
|
+
})}>
|
|
114
115
|
{entry}
|
|
115
116
|
{entry === value && <SelectionTick />}
|
|
116
117
|
</div>
|
|
@@ -1,27 +1,123 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { useTranslation } from 'react-i18next';
|
|
1
|
+
import React, { HTMLAttributes, useEffect, useRef, useState } from 'react';
|
|
3
2
|
import { Layer, Search, SearchProps } from '@carbon/react';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
4
|
import styles from './autosuggest.scss';
|
|
5
5
|
|
|
6
|
+
// FIXME Temporarily included types from Carbon
|
|
7
|
+
type InputPropsBase = Omit<HTMLAttributes<HTMLInputElement>, 'onChange'>;
|
|
8
|
+
interface SearchProps extends InputPropsBase {
|
|
9
|
+
/**
|
|
10
|
+
* Specify an optional value for the `autocomplete` property on the underlying
|
|
11
|
+
* `<input>`, defaults to "off"
|
|
12
|
+
*/
|
|
13
|
+
autoComplete?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Specify an optional className to be applied to the container node
|
|
17
|
+
*/
|
|
18
|
+
className?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Specify a label to be read by screen readers on the "close" button
|
|
22
|
+
*/
|
|
23
|
+
closeButtonLabelText?: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Optionally provide the default value of the `<input>`
|
|
27
|
+
*/
|
|
28
|
+
defaultValue?: string | number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Specify whether the `<input>` should be disabled
|
|
32
|
+
*/
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Specify whether or not ExpandableSearch should render expanded or not
|
|
37
|
+
*/
|
|
38
|
+
isExpanded?: boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Specify a custom `id` for the input
|
|
42
|
+
*/
|
|
43
|
+
id?: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Provide the label text for the Search icon
|
|
47
|
+
*/
|
|
48
|
+
labelText: React.ReactNode;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Optional callback called when the search value changes.
|
|
52
|
+
*/
|
|
53
|
+
onChange?(e: { target: HTMLInputElement; type: 'change' }): void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Optional callback called when the search value is cleared.
|
|
57
|
+
*/
|
|
58
|
+
onClear?(): void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Optional callback called when the magnifier icon is clicked in ExpandableSearch.
|
|
62
|
+
*/
|
|
63
|
+
onExpand?(e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Provide an optional placeholder text for the Search.
|
|
67
|
+
* Note: if the label and placeholder differ,
|
|
68
|
+
* VoiceOver on Mac will read both
|
|
69
|
+
*/
|
|
70
|
+
placeholder?: string;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Rendered icon for the Search.
|
|
74
|
+
* Can be a React component class
|
|
75
|
+
*/
|
|
76
|
+
renderIcon?: React.ComponentType | React.FunctionComponent;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Specify the role for the underlying `<input>`, defaults to `searchbox`
|
|
80
|
+
*/
|
|
81
|
+
role?: string;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Specify the size of the Search
|
|
85
|
+
*/
|
|
86
|
+
size?: 'sm' | 'md' | 'lg';
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Optional prop to specify the type of the `<input>`
|
|
90
|
+
*/
|
|
91
|
+
type?: string;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Specify the value of the `<input>`
|
|
95
|
+
*/
|
|
96
|
+
value?: string | number;
|
|
97
|
+
}
|
|
98
|
+
|
|
6
99
|
interface AutosuggestProps extends SearchProps {
|
|
7
100
|
getDisplayValue: Function;
|
|
8
101
|
getFieldValue: Function;
|
|
9
102
|
getSearchResults: (query: string) => Promise<any>;
|
|
10
103
|
onSuggestionSelected: (field: string, value: string) => void;
|
|
104
|
+
invalid?: boolean | undefined;
|
|
105
|
+
invalidText?: string | undefined;
|
|
11
106
|
}
|
|
12
107
|
|
|
13
|
-
export const Autosuggest: React.FC<
|
|
108
|
+
export const Autosuggest: React.FC<AutosuggestProps> = ({
|
|
14
109
|
getDisplayValue,
|
|
15
110
|
getFieldValue,
|
|
16
111
|
getSearchResults,
|
|
17
112
|
onSuggestionSelected,
|
|
113
|
+
invalid,
|
|
114
|
+
invalidText,
|
|
18
115
|
...searchProps
|
|
19
116
|
}) => {
|
|
20
117
|
const [suggestions, setSuggestions] = useState([]);
|
|
21
118
|
const searchBox = useRef(null);
|
|
22
119
|
const wrapper = useRef(null);
|
|
23
|
-
const {
|
|
24
|
-
const { name, labelText } = searchProps;
|
|
120
|
+
const { id: name, labelText } = searchProps;
|
|
25
121
|
|
|
26
122
|
useEffect(() => {
|
|
27
123
|
document.addEventListener('mousedown', handleClickOutsideComponent);
|
|
@@ -39,6 +135,7 @@ export const Autosuggest: React.FC<any> = ({
|
|
|
39
135
|
|
|
40
136
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
41
137
|
const query = e.target.value;
|
|
138
|
+
onSuggestionSelected(name, undefined);
|
|
42
139
|
if (query) {
|
|
43
140
|
getSearchResults(query).then((suggestions) => {
|
|
44
141
|
setSuggestions(suggestions);
|
|
@@ -48,6 +145,10 @@ export const Autosuggest: React.FC<any> = ({
|
|
|
48
145
|
}
|
|
49
146
|
};
|
|
50
147
|
|
|
148
|
+
const handleClear = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
149
|
+
onSuggestionSelected(name, undefined);
|
|
150
|
+
};
|
|
151
|
+
|
|
51
152
|
const handleClick = (index: number) => {
|
|
52
153
|
const display = getDisplayValue(suggestions[index]);
|
|
53
154
|
const value = getFieldValue(suggestions[index]);
|
|
@@ -59,10 +160,11 @@ export const Autosuggest: React.FC<any> = ({
|
|
|
59
160
|
return (
|
|
60
161
|
<div className={styles.autocomplete} ref={wrapper}>
|
|
61
162
|
<label className="cds--label">{labelText}</label>
|
|
62
|
-
<Layer>
|
|
163
|
+
<Layer className={classNames({ [styles.invalid]: invalid })}>
|
|
63
164
|
<Search
|
|
64
165
|
id="autosuggest"
|
|
65
166
|
onChange={handleChange}
|
|
167
|
+
onClear={handleClear}
|
|
66
168
|
ref={searchBox}
|
|
67
169
|
className={styles.autocompleteSearch}
|
|
68
170
|
{...searchProps}
|
|
@@ -79,6 +181,7 @@ export const Autosuggest: React.FC<any> = ({
|
|
|
79
181
|
))}
|
|
80
182
|
</ul>
|
|
81
183
|
)}
|
|
184
|
+
{invalid ? <label className={classNames(styles.invalidMsg)}>{invalidText}</label> : <></>}
|
|
82
185
|
</div>
|
|
83
186
|
);
|
|
84
187
|
};
|
|
@@ -38,7 +38,7 @@ describe('autosuggest', () => {
|
|
|
38
38
|
<BrowserRouter>
|
|
39
39
|
<Autosuggest
|
|
40
40
|
labelText=""
|
|
41
|
-
|
|
41
|
+
id="person"
|
|
42
42
|
placeholder="Find Person"
|
|
43
43
|
onSuggestionSelected={handleSuggestionSelected}
|
|
44
44
|
getSearchResults={mockGetSearchResults}
|
|
@@ -79,7 +79,7 @@ describe('autosuggest', () => {
|
|
|
79
79
|
fireEvent.change(searchbox, { target: { value: 'john' } });
|
|
80
80
|
const listitems = await waitFor(() => screen.getAllByRole('listitem'));
|
|
81
81
|
fireEvent.click(listitems[0]);
|
|
82
|
-
expect(handleSuggestionSelected).toHaveBeenNthCalledWith(
|
|
82
|
+
expect(handleSuggestionSelected).toHaveBeenNthCalledWith(4, 'person', 'randomuuid1');
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
it('should clear the suggestions when a suggestion is selected', async () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
2
3
|
import { v4 } from 'uuid';
|
|
3
4
|
import { FormValues } from '../../patient-registration.types';
|
|
4
5
|
import styles from './../input.scss';
|
|
@@ -41,8 +42,8 @@ export const DummyDataInput: React.FC<DummyDataInputProps> = ({ setValues }) =>
|
|
|
41
42
|
return (
|
|
42
43
|
<main>
|
|
43
44
|
<button
|
|
45
|
+
className={classNames('omrs-btn omrs-filled-neutral', styles.dummyData)}
|
|
44
46
|
onClick={() => setValues(dummyFormValues)}
|
|
45
|
-
className={`omrs-btn omrs-filled-neutral ${styles.dummyData}`}
|
|
46
47
|
type="button"
|
|
47
48
|
aria-label="Dummy Data Input">
|
|
48
49
|
Input Dummy Data
|
|
@@ -1,28 +1,19 @@
|
|
|
1
1
|
import React, { useState, useEffect, useContext, useMemo, useRef } from 'react';
|
|
2
|
-
import
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Button, Link, InlineLoading } from '@carbon/react';
|
|
3
4
|
import { XAxis, ShareKnowledge } from '@carbon/react/icons';
|
|
4
|
-
import {
|
|
5
|
+
import { useLocation, useParams } from 'react-router-dom';
|
|
5
6
|
import { useTranslation } from 'react-i18next';
|
|
6
7
|
import { Formik, Form, FormikHelpers } from 'formik';
|
|
7
|
-
import { createErrorHandler,
|
|
8
|
+
import { createErrorHandler, showSnackbar, useConfig, interpolateUrl, usePatient } from '@openmrs/esm-framework';
|
|
8
9
|
import { validationSchema as initialSchema } from './validation/patient-registration-validation';
|
|
9
|
-
import { FormValues, CapturePhotoProps
|
|
10
|
+
import { FormValues, CapturePhotoProps } from './patient-registration.types';
|
|
10
11
|
import { PatientRegistrationContext } from './patient-registration-context';
|
|
11
12
|
import { SavePatientForm, SavePatientTransactionManager } from './form-manager';
|
|
12
13
|
import { usePatientPhoto } from './patient-registration.resource';
|
|
13
14
|
import { DummyDataInput } from './input/dummy-data/dummy-data-input.component';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
filterUndefinedPatientIdenfier,
|
|
17
|
-
parseAddressTemplateXml,
|
|
18
|
-
scrollIntoView,
|
|
19
|
-
} from './patient-registration-utils';
|
|
20
|
-
import {
|
|
21
|
-
useInitialAddressFieldValues,
|
|
22
|
-
useInitialFormValues,
|
|
23
|
-
usePatientObs,
|
|
24
|
-
usePatientUuidMap,
|
|
25
|
-
} from './patient-registration-hooks';
|
|
15
|
+
import { cancelRegistration, filterUndefinedPatientIdenfier, scrollIntoView } from './patient-registration-utils';
|
|
16
|
+
import { useInitialAddressFieldValues, useInitialFormValues, usePatientUuidMap } from './patient-registration-hooks';
|
|
26
17
|
import { ResourcesContext } from '../offline.resources';
|
|
27
18
|
import { builtInSections, RegistrationConfig, SectionDefinition } from '../config-schema';
|
|
28
19
|
import { SectionWrapper } from './section/section-wrapper.component';
|
|
@@ -95,17 +86,18 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
95
86
|
abortController,
|
|
96
87
|
);
|
|
97
88
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
? t('
|
|
89
|
+
showSnackbar({
|
|
90
|
+
subtitle: inEditMode
|
|
91
|
+
? t('updatePatientSuccessSnackbarSubtitle', "The patient's information has been successfully updated")
|
|
101
92
|
: t(
|
|
102
|
-
'
|
|
93
|
+
'registerPatientSuccessSnackbarSubtitle',
|
|
103
94
|
'The patient can now be found by searching for them using their name or ID number',
|
|
104
95
|
),
|
|
105
96
|
title: inEditMode
|
|
106
|
-
? t('
|
|
107
|
-
: t('
|
|
97
|
+
? t('updatePatientSuccessSnackbarTitle', 'Patient Details Updated')
|
|
98
|
+
: t('registerPatientSuccessSnackbarTitle', 'New Patient Created'),
|
|
108
99
|
kind: 'success',
|
|
100
|
+
isLowContrast: true,
|
|
109
101
|
});
|
|
110
102
|
|
|
111
103
|
const afterUrl = new URLSearchParams(search).get('afterUrl');
|
|
@@ -115,10 +107,22 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
115
107
|
} catch (error) {
|
|
116
108
|
if (error.responseBody?.error?.globalErrors) {
|
|
117
109
|
error.responseBody.error.globalErrors.forEach((error) => {
|
|
118
|
-
|
|
110
|
+
showSnackbar({
|
|
111
|
+
title: inEditMode
|
|
112
|
+
? t('updatePatientErrorSnackbarTitle', 'Patient Details Update Failed')
|
|
113
|
+
: t('registrationErrorSnackbarTitle', 'Patient Registration Failed'),
|
|
114
|
+
subtitle: error.message,
|
|
115
|
+
kind: 'error',
|
|
116
|
+
});
|
|
119
117
|
});
|
|
120
118
|
} else if (error.responseBody?.error?.message) {
|
|
121
|
-
|
|
119
|
+
showSnackbar({
|
|
120
|
+
title: inEditMode
|
|
121
|
+
? t('updatePatientErrorSnackbarTitle', 'Patient Details Update Failed')
|
|
122
|
+
: t('registrationErrorSnackbarTitle', 'Patient Registration Failed'),
|
|
123
|
+
subtitle: error.responseBody.error.message,
|
|
124
|
+
kind: 'error',
|
|
125
|
+
});
|
|
122
126
|
} else {
|
|
123
127
|
createErrorHandler()(error);
|
|
124
128
|
}
|
|
@@ -127,25 +131,15 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
127
131
|
}
|
|
128
132
|
};
|
|
129
133
|
|
|
130
|
-
const getDescription = (errors) => {
|
|
131
|
-
return (
|
|
132
|
-
<div>
|
|
133
|
-
<p>{t('fieldErrorTitleMessage', 'The following fields have errors:')}</p>
|
|
134
|
-
<ul style={{ listStyle: 'inside' }}>
|
|
135
|
-
{Object.keys(errors).map((error, index) => (
|
|
136
|
-
<li key={index}>{t(`${error}LabelText`, error)}</li>
|
|
137
|
-
))}
|
|
138
|
-
</ul>
|
|
139
|
-
</div>
|
|
140
|
-
);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
134
|
const displayErrors = (errors) => {
|
|
144
135
|
if (errors && typeof errors === 'object' && !!Object.keys(errors).length) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
Object.keys(errors).forEach((error) => {
|
|
137
|
+
showSnackbar({
|
|
138
|
+
subtitle: t(`${error}LabelText`, error),
|
|
139
|
+
title: t('incompleteForm', 'The following field has errors:'),
|
|
140
|
+
kind: 'warning',
|
|
141
|
+
isLowContrast: true,
|
|
142
|
+
});
|
|
149
143
|
});
|
|
150
144
|
}
|
|
151
145
|
};
|
|
@@ -168,7 +162,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
168
162
|
{showDummyData && <DummyDataInput setValues={props.setValues} />}
|
|
169
163
|
<p className={styles.label01}>{t('jumpTo', 'Jump to')}</p>
|
|
170
164
|
{sections.map((section) => (
|
|
171
|
-
<div className={
|
|
165
|
+
<div className={classNames(styles.space05, styles.touchTarget)} key={section.name}>
|
|
172
166
|
<Link className={styles.linkName} onClick={() => scrollIntoView(section.id)}>
|
|
173
167
|
<XAxis size={16} /> {t(`${section.id}Section`, section.name)}
|
|
174
168
|
</Link>
|
|
@@ -193,8 +187,19 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
193
187
|
// Current session and identifiers are required for patient registration.
|
|
194
188
|
// If currentSession or identifierTypes are not available, then the
|
|
195
189
|
// user should be blocked to register the patient.
|
|
196
|
-
disabled={!enableClientRegistry}>
|
|
197
|
-
{
|
|
190
|
+
disabled={!currentSession || !identifierTypes || props.isSubmitting || !enableClientRegistry}>
|
|
191
|
+
{props.isSubmitting ? (
|
|
192
|
+
<InlineLoading
|
|
193
|
+
className={styles.spinner}
|
|
194
|
+
description={`${t('submitting', 'Submitting')} ...`}
|
|
195
|
+
iconDescription="submitting"
|
|
196
|
+
status="active"
|
|
197
|
+
/>
|
|
198
|
+
) : inEditMode ? (
|
|
199
|
+
t('updatePatient', 'Update Patient')
|
|
200
|
+
) : (
|
|
201
|
+
t('registerPatient', 'Register Patient')
|
|
202
|
+
)}
|
|
198
203
|
</Button>
|
|
199
204
|
<Button className={styles.cancelButton} kind="tertiary" onClick={cancelRegistration}>
|
|
200
205
|
{t('cancel', 'Cancel')}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
1
|
import useSWR from 'swr';
|
|
3
|
-
import
|
|
4
|
-
import { FetchResponse, openmrsFetch, useConfig } from '@openmrs/esm-framework';
|
|
2
|
+
import { openmrsFetch, useConfig } from '@openmrs/esm-framework';
|
|
5
3
|
import { Patient, Relationship, PatientIdentifier, Encounter } from './patient-registration.types';
|
|
6
4
|
|
|
7
5
|
export const uuidIdentifier = '05a29f94-c0ed-11e2-94be-8c13b969e334';
|
|
@@ -181,12 +179,25 @@ export function usePatientPhoto(patientUuid: string): UsePatientPhotoResult {
|
|
|
181
179
|
};
|
|
182
180
|
}
|
|
183
181
|
|
|
184
|
-
export async function fetchPerson(query: string) {
|
|
185
|
-
const
|
|
182
|
+
export async function fetchPerson(query: string, abortController: AbortController) {
|
|
183
|
+
const [patientsRes, personsRes] = await Promise.all([
|
|
184
|
+
openmrsFetch(`/ws/rest/v1/patient?q=${query}`, {
|
|
185
|
+
signal: abortController.signal,
|
|
186
|
+
}),
|
|
187
|
+
openmrsFetch(`/ws/rest/v1/person?q=${query}`, {
|
|
188
|
+
signal: abortController.signal,
|
|
189
|
+
}),
|
|
190
|
+
]);
|
|
186
191
|
|
|
187
|
-
|
|
188
|
-
|
|
192
|
+
const results = [...patientsRes.data.results];
|
|
193
|
+
|
|
194
|
+
personsRes.data.results.forEach((person) => {
|
|
195
|
+
if (!results.some((patient) => patient.uuid === person.uuid)) {
|
|
196
|
+
results.push(person);
|
|
197
|
+
}
|
|
189
198
|
});
|
|
199
|
+
|
|
200
|
+
return results;
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
export async function addPatientIdentifier(patientUuid: string, patientIdentifier: PatientIdentifier) {
|