@digihmis/esm-outpatient-app 1.0.0
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/README.md +4 -0
- package/dist/117.js +1 -0
- package/dist/117.js.map +1 -0
- package/dist/132.js +1 -0
- package/dist/132.js.map +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/209.js +1 -0
- package/dist/209.js.map +1 -0
- package/dist/317.js +1 -0
- package/dist/317.js.map +1 -0
- package/dist/349.js +1 -0
- package/dist/349.js.map +1 -0
- package/dist/371.js +1 -0
- package/dist/371.js.map +1 -0
- package/dist/378.js +1 -0
- package/dist/378.js.map +1 -0
- package/dist/395.js +12 -0
- package/dist/395.js.map +1 -0
- package/dist/442.js +1 -0
- package/dist/442.js.map +1 -0
- package/dist/45.js +1 -0
- package/dist/45.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/508.js +1 -0
- package/dist/508.js.map +1 -0
- package/dist/564.js +1 -0
- package/dist/564.js.map +1 -0
- package/dist/61.js +1 -0
- package/dist/61.js.map +1 -0
- package/dist/640.js +1 -0
- package/dist/640.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/709.js +1 -0
- package/dist/709.js.map +1 -0
- package/dist/712.js +1 -0
- package/dist/712.js.map +1 -0
- package/dist/720.js +1 -0
- package/dist/720.js.map +1 -0
- package/dist/742.js +1 -0
- package/dist/742.js.map +1 -0
- package/dist/759.js +15 -0
- package/dist/759.js.map +1 -0
- package/dist/771.js +1 -0
- package/dist/771.js.map +1 -0
- package/dist/797.js +1 -0
- package/dist/797.js.map +1 -0
- package/dist/806.js +1 -0
- package/dist/806.js.map +1 -0
- package/dist/912.js +1 -0
- package/dist/912.js.map +1 -0
- package/dist/913.js +1 -0
- package/dist/913.js.map +1 -0
- package/dist/940.js +1 -0
- package/dist/940.js.map +1 -0
- package/dist/974.js +1 -0
- package/dist/974.js.map +1 -0
- package/dist/digihmis-esm-outpatient-app.js +6 -0
- package/dist/digihmis-esm-outpatient-app.js.buildmanifest.json +827 -0
- package/dist/digihmis-esm-outpatient-app.js.map +1 -0
- package/dist/main.js +17 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +26 -0
- package/package.json +51 -0
- package/rspack.config.js +1 -0
- package/src/config-schema.ts +347 -0
- package/src/dashboard.meta.ts +16 -0
- package/src/declarations.d.ts +6 -0
- package/src/icon-wrapper/icon-wrapper.extension.tsx +17 -0
- package/src/index.ts +27 -0
- package/src/left-panel/left-panel.component.tsx +100 -0
- package/src/left-panel/left-panel.scss +13 -0
- package/src/main-dashboard/outpatient-dashboard.component.tsx +264 -0
- package/src/main-dashboard/outpatient-dashboard.resource.ts +71 -0
- package/src/main-dashboard/outpatient-dashboard.scss +166 -0
- package/src/main-dashboard/type/index.ts +162 -0
- package/src/page-header/page-header.component.tsx +35 -0
- package/src/page-header/page-header.scss +76 -0
- package/src/patient-management/components/auto-suggest/autosuggest.component.tsx +149 -0
- package/src/patient-management/components/auto-suggest/autosuggest.scss +61 -0
- package/src/patient-management/components/auto-suggest/diagnosis-autosuggest.component.tsx +131 -0
- package/src/patient-management/components/auto-suggest/diagnosis-autosuggest.scss +42 -0
- package/src/patient-management/patient-banner/patient-banner.component.tsx +60 -0
- package/src/patient-management/patient-banner/patient-banner.scss +72 -0
- package/src/patient-management/patient-management-workspace/billable-services-selector.component.tsx +150 -0
- package/src/patient-management/patient-management-workspace/patient-management-schema/index.ts +261 -0
- package/src/patient-management/patient-management-workspace/patient-management-workspace.scss +304 -0
- package/src/patient-management/patient-management-workspace/patient-management-workspace.tsx +1581 -0
- package/src/patient-management/patient-management-workspace/patient-management.resource.ts +182 -0
- package/src/patient-management/patient-management-workspace/type/index.ts +193 -0
- package/src/patient-management/patient-management-workspace/utils/index.ts +172 -0
- package/src/patient-management/patient-management.component.tsx +97 -0
- package/src/patient-management/patient-management.scss +30 -0
- package/src/patient-management/utils/index.ts +5 -0
- package/src/queue-room-table/queue-room-table.component.tsx +313 -0
- package/src/queue-room-table/queue-room-table.resource.ts +73 -0
- package/src/queue-room-table/queue-room-table.scss +193 -0
- package/src/queue-room-table/type/index.ts +34 -0
- package/src/queue-room-table/useQueueActions.ts +24 -0
- package/src/routes.json +55 -0
- package/src/setup-tests.ts +1 -0
- package/translations/am.json +57 -0
- package/translations/ar.json +57 -0
- package/translations/en.json +57 -0
- package/translations/es.json +57 -0
- package/translations/fr.json +57 -0
- package/translations/he.json +57 -0
- package/translations/km.json +57 -0
- package/translations/pt.json +57 -0
- package/translations/sw.json +57 -0
- package/translations/vi.json +57 -0
- package/translations/zh.json +57 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.header {
|
|
6
|
+
@include type.type-style('body-compact-02');
|
|
7
|
+
height: layout.$spacing-12;
|
|
8
|
+
border: 1px solid colors.$gray-20;
|
|
9
|
+
border-left: 0px;
|
|
10
|
+
display: flex;
|
|
11
|
+
background: var(--cds-field);
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
margin-top: 1.5rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.left-justified-items {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: row;
|
|
19
|
+
align-items: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.right-justified-items {
|
|
23
|
+
@include type.type-style('body-01');
|
|
24
|
+
color: colors.$gray-80;
|
|
25
|
+
padding-top: layout.$spacing-05;
|
|
26
|
+
padding-bottom: layout.$spacing-07;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.page-subtitle {
|
|
30
|
+
@include type.type-style('caption-01');
|
|
31
|
+
color: colors.$gray-70;
|
|
32
|
+
font-size: layout.$spacing-06;
|
|
33
|
+
}
|
|
34
|
+
.page-name {
|
|
35
|
+
@include type.type-style('caption-01');
|
|
36
|
+
color: colors.$gray-80;
|
|
37
|
+
font-size: layout.$spacing-05;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.page-labels {
|
|
41
|
+
p:first-of-type {
|
|
42
|
+
margin-bottom: layout.$spacing-02;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.date-and-location {
|
|
47
|
+
margin-top: layout.$spacing-03;
|
|
48
|
+
display: flex;
|
|
49
|
+
justify-content: flex-end;
|
|
50
|
+
align-items: center;
|
|
51
|
+
margin-right: layout.$spacing-05;
|
|
52
|
+
@include type.type-style('caption-01');
|
|
53
|
+
font-size: 0.83rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.value {
|
|
57
|
+
margin-left: layout.$spacing-02;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.middot {
|
|
61
|
+
margin: 0 layout.$spacing-03;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.view {
|
|
65
|
+
@include type.type-style('caption-01');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
svg.iconOverrides {
|
|
69
|
+
width: 72 !important;
|
|
70
|
+
height: 72 !important;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.svgContainer svg {
|
|
74
|
+
width: 72px;
|
|
75
|
+
height: 72px;
|
|
76
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { InlineLoading, Layer, Search } from '@carbon/react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import React, { type HTMLAttributes, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import styles from './autosuggest.scss';
|
|
6
|
+
|
|
7
|
+
type InputPropsBase = Omit<HTMLAttributes<HTMLInputElement>, 'onChange'>;
|
|
8
|
+
|
|
9
|
+
interface SearchProps extends InputPropsBase {
|
|
10
|
+
autoComplete?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
closeButtonLabelText?: string;
|
|
13
|
+
defaultValue?: string | number;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
isExpanded?: boolean;
|
|
16
|
+
id?: string;
|
|
17
|
+
labelText: React.ReactNode;
|
|
18
|
+
onChange?(e: { target: HTMLInputElement; type: 'change' }): void;
|
|
19
|
+
onClear?(): void;
|
|
20
|
+
onExpand?(e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
renderIcon?: React.ComponentType | React.FunctionComponent;
|
|
23
|
+
role?: string;
|
|
24
|
+
size?: 'sm' | 'md' | 'lg';
|
|
25
|
+
type?: string;
|
|
26
|
+
value?: string | number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface AutosuggestProps extends SearchProps {
|
|
30
|
+
getDisplayValue: (item: any) => string;
|
|
31
|
+
getFieldValue: (item: any) => string;
|
|
32
|
+
getSearchResults: (query: string) => Promise<any>;
|
|
33
|
+
onSuggestionSelected: (field: string, value: string) => void;
|
|
34
|
+
invalid?: boolean;
|
|
35
|
+
invalidText?: string;
|
|
36
|
+
renderSuggestionItem?: (item: any) => React.ReactNode;
|
|
37
|
+
renderEmptyState?: (value: any) => React.ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const Autosuggest: React.FC<AutosuggestProps> = ({
|
|
41
|
+
getDisplayValue,
|
|
42
|
+
getFieldValue,
|
|
43
|
+
getSearchResults,
|
|
44
|
+
onSuggestionSelected,
|
|
45
|
+
invalid,
|
|
46
|
+
invalidText,
|
|
47
|
+
renderSuggestionItem,
|
|
48
|
+
renderEmptyState,
|
|
49
|
+
...searchProps
|
|
50
|
+
}) => {
|
|
51
|
+
const { t } = useTranslation();
|
|
52
|
+
const [suggestions, setSuggestions] = useState<any[]>([]);
|
|
53
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
54
|
+
const searchBox = useRef<HTMLInputElement>(null);
|
|
55
|
+
const wrapper = useRef<HTMLDivElement>(null);
|
|
56
|
+
const { id: name, labelText } = searchProps;
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const handleClickOutsideComponent = (e: MouseEvent) => {
|
|
60
|
+
if (wrapper.current && !wrapper.current.contains(e.target as Node)) {
|
|
61
|
+
setSuggestions([]);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
document.addEventListener('mousedown', handleClickOutsideComponent);
|
|
66
|
+
return () => {
|
|
67
|
+
document.removeEventListener('mousedown', handleClickOutsideComponent);
|
|
68
|
+
};
|
|
69
|
+
}, [wrapper]);
|
|
70
|
+
|
|
71
|
+
const handleChange = (e) => {
|
|
72
|
+
const query = e.target.value;
|
|
73
|
+
onSuggestionSelected(name, undefined);
|
|
74
|
+
|
|
75
|
+
if (query && query.trim()) {
|
|
76
|
+
setIsLoading(true);
|
|
77
|
+
getSearchResults(query.trim())
|
|
78
|
+
.then((results) => {
|
|
79
|
+
setSuggestions(results || []);
|
|
80
|
+
setIsLoading(false);
|
|
81
|
+
})
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
setSuggestions([]);
|
|
84
|
+
setIsLoading(false);
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
setSuggestions([]);
|
|
88
|
+
setIsLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleClear = () => {
|
|
93
|
+
onSuggestionSelected(name, undefined);
|
|
94
|
+
setSuggestions([]);
|
|
95
|
+
setIsLoading(false);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleClick = (index: number) => {
|
|
99
|
+
const selectedSuggestion = suggestions[index];
|
|
100
|
+
if (selectedSuggestion) {
|
|
101
|
+
const fieldValue = getFieldValue(selectedSuggestion);
|
|
102
|
+
const displayValue = getDisplayValue(selectedSuggestion);
|
|
103
|
+
if (searchBox.current) {
|
|
104
|
+
searchBox.current.value = displayValue;
|
|
105
|
+
}
|
|
106
|
+
onSuggestionSelected(name, fieldValue);
|
|
107
|
+
setSuggestions([]);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className={styles.autocomplete} ref={wrapper}>
|
|
113
|
+
<label className="cds--label">{labelText}</label>
|
|
114
|
+
<Layer className={classNames({ [styles.invalid]: invalid })}>
|
|
115
|
+
<Search
|
|
116
|
+
id="autosuggest"
|
|
117
|
+
onChange={handleChange}
|
|
118
|
+
onClear={handleClear}
|
|
119
|
+
ref={searchBox}
|
|
120
|
+
className={styles.autocompleteSearch}
|
|
121
|
+
{...searchProps}
|
|
122
|
+
/>
|
|
123
|
+
</Layer>
|
|
124
|
+
{isLoading && (
|
|
125
|
+
<div className={styles.loading}>
|
|
126
|
+
<InlineLoading status="active" description={t('loading', 'Loading...')} />
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
{suggestions.length > 0 ? (
|
|
130
|
+
<ul className={styles.suggestions}>
|
|
131
|
+
{suggestions.map((suggestion, index) => {
|
|
132
|
+
return (
|
|
133
|
+
<li
|
|
134
|
+
key={getFieldValue(suggestion) || index}
|
|
135
|
+
onClick={() => handleClick(index)}
|
|
136
|
+
role="presentation"
|
|
137
|
+
style={{ cursor: 'pointer' }}>
|
|
138
|
+
{renderSuggestionItem ? renderSuggestionItem(suggestion) : getDisplayValue(suggestion)}
|
|
139
|
+
</li>
|
|
140
|
+
);
|
|
141
|
+
})}
|
|
142
|
+
</ul>
|
|
143
|
+
) : (
|
|
144
|
+
!isLoading && renderEmptyState && renderEmptyState(searchBox.current?.value || '')
|
|
145
|
+
)}
|
|
146
|
+
{invalid && <label className={classNames(styles.invalidMsg)}>{invalidText}</label>}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.label01 {
|
|
6
|
+
@include type.type-style('label-01');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.suggestions {
|
|
10
|
+
position: relative;
|
|
11
|
+
border-top-width: 0;
|
|
12
|
+
list-style: none;
|
|
13
|
+
margin-top: 0;
|
|
14
|
+
max-height: 400px;
|
|
15
|
+
overflow-y: auto;
|
|
16
|
+
padding-left: 0;
|
|
17
|
+
width: 100%;
|
|
18
|
+
position: absolute;
|
|
19
|
+
left: 0;
|
|
20
|
+
background-color: colors.$white;
|
|
21
|
+
margin-bottom: 20px;
|
|
22
|
+
z-index: 99;
|
|
23
|
+
box-shadow: layout.$spacing-03 layout.$spacing-03 layout.$spacing-03 layout.$spacing-03 colors.$gray-30;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.suggestions li {
|
|
27
|
+
color: colors.$gray-70;
|
|
28
|
+
border-bottom: 1px solid colors.$gray-50;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.displayText {
|
|
32
|
+
padding: layout.$spacing-05;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.suggestions li:not(:last-of-type) {
|
|
36
|
+
border-bottom: 1px solid colors.$gray-40-hover;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.autocomplete {
|
|
40
|
+
position: relative;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.autocompleteSearch {
|
|
44
|
+
width: 100%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.suggestions a {
|
|
48
|
+
color: inherit;
|
|
49
|
+
text-decoration: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.invalid input {
|
|
53
|
+
outline: 2px solid var(--cds-support-error, colors.$red-60);
|
|
54
|
+
outline-offset: -2px;
|
|
55
|
+
margin-bottom: layout.$spacing-02;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.invalidMsg {
|
|
59
|
+
color: var(--cds-text-error, colors.$red-60);
|
|
60
|
+
font-size: small;
|
|
61
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Tag } from '@carbon/react';
|
|
4
|
+
import styles from './diagnosis-autosuggest.scss';
|
|
5
|
+
import { GenericItem } from '../../patient-management-workspace/patient-management-schema';
|
|
6
|
+
import { Autosuggest } from './autosuggest.component';
|
|
7
|
+
import { useDiagnosis } from '../../patient-management-workspace/patient-management.resource';
|
|
8
|
+
|
|
9
|
+
interface DiagnosisAutosuggestProps {
|
|
10
|
+
id: string;
|
|
11
|
+
labelText: string;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
selectedDiagnoses: Array<GenericItem>;
|
|
14
|
+
onChange: (diagnoses: Array<GenericItem>) => void;
|
|
15
|
+
invalid?: boolean;
|
|
16
|
+
invalidText?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DiagnosisAutosuggest: React.FC<DiagnosisAutosuggestProps> = ({
|
|
20
|
+
id,
|
|
21
|
+
labelText,
|
|
22
|
+
placeholder = 'Search for diagnosis',
|
|
23
|
+
selectedDiagnoses,
|
|
24
|
+
onChange,
|
|
25
|
+
invalid = false,
|
|
26
|
+
invalidText,
|
|
27
|
+
}) => {
|
|
28
|
+
const { t } = useTranslation();
|
|
29
|
+
const [searchQuery, setSearchQuery] = React.useState('');
|
|
30
|
+
const { diagnoses, isLoading } = useDiagnosis(searchQuery);
|
|
31
|
+
|
|
32
|
+
const diagnosesRef = useRef<any[]>([]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (diagnoses && diagnoses.length > 0) {
|
|
36
|
+
diagnosesRef.current = diagnoses;
|
|
37
|
+
}
|
|
38
|
+
}, [diagnoses]);
|
|
39
|
+
|
|
40
|
+
const getSearchResults = async (query: string): Promise<any[]> => {
|
|
41
|
+
setSearchQuery(query);
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
43
|
+
return diagnoses || [];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleSuggestionSelected = (fieldName: string, value: string) => {
|
|
47
|
+
if (value) {
|
|
48
|
+
const selectedDiagnosis = diagnosesRef.current.find((d) => d.uuid === value);
|
|
49
|
+
|
|
50
|
+
if (selectedDiagnosis) {
|
|
51
|
+
const newDiagnosis: GenericItem = {
|
|
52
|
+
id: selectedDiagnosis.uuid,
|
|
53
|
+
text: selectedDiagnosis.display,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const alreadySelected = selectedDiagnoses.some((d) => d.id === value);
|
|
57
|
+
|
|
58
|
+
if (!alreadySelected) {
|
|
59
|
+
const updated = [...selectedDiagnoses, newDiagnosis];
|
|
60
|
+
onChange(updated);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleRemoveDiagnosis = (diagnosisId: string) => {
|
|
67
|
+
const updated = selectedDiagnoses.filter((d) => d.id !== diagnosisId);
|
|
68
|
+
onChange(updated);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className={styles.diagnosisAutosuggestContainer}>
|
|
73
|
+
{selectedDiagnoses.length > 0 && (
|
|
74
|
+
<div className={styles.selectedDiagnosesTags}>
|
|
75
|
+
{selectedDiagnoses.map((diagnosis) => (
|
|
76
|
+
<Tag
|
|
77
|
+
key={diagnosis.id}
|
|
78
|
+
type="cyan"
|
|
79
|
+
filter
|
|
80
|
+
onClose={() => handleRemoveDiagnosis(diagnosis.id)}
|
|
81
|
+
className={styles.diagnosisTag}>
|
|
82
|
+
{diagnosis.text}
|
|
83
|
+
</Tag>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
<Autosuggest
|
|
89
|
+
id={id}
|
|
90
|
+
labelText={labelText}
|
|
91
|
+
placeholder={placeholder}
|
|
92
|
+
getDisplayValue={(item) => item.display}
|
|
93
|
+
getFieldValue={(item) => item.uuid}
|
|
94
|
+
getSearchResults={getSearchResults}
|
|
95
|
+
onSuggestionSelected={handleSuggestionSelected}
|
|
96
|
+
invalid={invalid}
|
|
97
|
+
invalidText={invalidText}
|
|
98
|
+
renderSuggestionItem={(item) => (
|
|
99
|
+
<div className={styles.suggestionItem}>
|
|
100
|
+
<span className={styles.diagnosisDisplay}>{item.display}</span>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
renderEmptyState={(value) => {
|
|
104
|
+
if (!value || value.trim().length === 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (value.length < 3) {
|
|
109
|
+
return (
|
|
110
|
+
<div className={styles.emptyState}>{t('typeMoreCharacters', 'Type at least 3 characters to search')}</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (isLoading) {
|
|
115
|
+
return <div className={styles.emptyState}>{t('searchingDiagnosis', 'Searching...')}</div>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (searchQuery.length >= 3 && (!diagnoses || diagnoses.length === 0)) {
|
|
119
|
+
return (
|
|
120
|
+
<div className={styles.emptyState}>
|
|
121
|
+
{t('noDiagnosisFound', 'No diagnosis codes found. Try different keywords.')}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return null;
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// diagnosis-autosuggest.scss
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.diagnosisAutosuggestContainer {
|
|
6
|
+
margin-bottom: layout.$spacing-05;
|
|
7
|
+
|
|
8
|
+
.selectedDiagnosesTags {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
gap: layout.$spacing-03;
|
|
12
|
+
margin-bottom: layout.$spacing-04;
|
|
13
|
+
|
|
14
|
+
.diagnosisTag {
|
|
15
|
+
max-width: 100%;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.suggestionItem {
|
|
20
|
+
padding: layout.$spacing-04 layout.$spacing-05;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
transition: background-color 0.1s;
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
background-color: colors.$gray-20;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.diagnosisDisplay {
|
|
29
|
+
font-size: 0.875rem;
|
|
30
|
+
color: colors.$gray-100;
|
|
31
|
+
line-height: 1.25rem;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.emptyState {
|
|
36
|
+
padding: layout.$spacing-05;
|
|
37
|
+
text-align: center;
|
|
38
|
+
color: colors.$gray-70;
|
|
39
|
+
font-size: 0.875rem;
|
|
40
|
+
font-style: italic;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import {
|
|
4
|
+
getPatientName,
|
|
5
|
+
PatientBannerPatientInfo,
|
|
6
|
+
PatientBannerToggleContactDetailsButton,
|
|
7
|
+
PatientPhoto,
|
|
8
|
+
} from '@openmrs/esm-framework';
|
|
9
|
+
import styles from './patient-banner.scss';
|
|
10
|
+
|
|
11
|
+
interface PatientBannerProps {
|
|
12
|
+
patient: fhir.Patient;
|
|
13
|
+
patientUuid: string;
|
|
14
|
+
hideActionsOverflow?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PatientBanner: React.FC<PatientBannerProps> = ({ patient, patientUuid }) => {
|
|
18
|
+
const patientBannerRef = useRef(null);
|
|
19
|
+
const [isTabletViewport, setIsTabletViewport] = useState(false);
|
|
20
|
+
const [showContactDetails, setShowContactDetails] = useState(false);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const currentRef = patientBannerRef.current;
|
|
24
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
setIsTabletViewport(entry.contentRect.width < 1023);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
resizeObserver.observe(patientBannerRef.current);
|
|
30
|
+
return () => {
|
|
31
|
+
if (currentRef) {
|
|
32
|
+
resizeObserver.unobserve(currentRef);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}, [patientBannerRef, setIsTabletViewport]);
|
|
36
|
+
|
|
37
|
+
const patientName = patient ? getPatientName(patient) : '';
|
|
38
|
+
|
|
39
|
+
const isDeceased = Boolean(patient?.deceasedDateTime);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<header
|
|
43
|
+
aria-label="patient banner"
|
|
44
|
+
className={classNames(
|
|
45
|
+
styles.container,
|
|
46
|
+
isDeceased ? styles.deceasedPatientContainer : styles.activePatientContainer,
|
|
47
|
+
)}
|
|
48
|
+
role="banner"
|
|
49
|
+
ref={patientBannerRef}>
|
|
50
|
+
<div className={styles.patientBanner}>
|
|
51
|
+
<div className={styles.patientAvatar}>
|
|
52
|
+
<PatientPhoto patientUuid={patientUuid} patientName={patientName} />
|
|
53
|
+
</div>
|
|
54
|
+
<PatientBannerPatientInfo patient={patient} renderedFrom="patient-chart" />
|
|
55
|
+
</div>
|
|
56
|
+
</header>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default PatientBanner;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
border-top: 0.063rem solid $ui-03;
|
|
7
|
+
border-bottom: 0.063rem solid $ui-03;
|
|
8
|
+
background-color: $ui-01;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.activePatientContainer,
|
|
12
|
+
.deceasedPatientContainer {
|
|
13
|
+
background-color: $ui-01;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.buttonCol {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
justify-content: space-between;
|
|
20
|
+
align-items: flex-end;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.patientBanner {
|
|
24
|
+
display: flex;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.patientAvatar {
|
|
28
|
+
width: 3.5rem;
|
|
29
|
+
height: 3.5rem;
|
|
30
|
+
margin: layout.$spacing-05;
|
|
31
|
+
border-radius: 1px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.patientAvatarButton {
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
border: none;
|
|
37
|
+
padding: 0px;
|
|
38
|
+
background: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.contactDetails {
|
|
42
|
+
color: $text-02;
|
|
43
|
+
width: 100%;
|
|
44
|
+
display: flex;
|
|
45
|
+
|
|
46
|
+
a {
|
|
47
|
+
@include type.type-style('body-compact-01');
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.tabletContactDetails {
|
|
53
|
+
display: block;
|
|
54
|
+
|
|
55
|
+
.row {
|
|
56
|
+
&:first-child {
|
|
57
|
+
border-bottom: 1px solid $ui-03;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.row > .col {
|
|
62
|
+
&:nth-of-type(2n + 1) {
|
|
63
|
+
border-right: 1px solid $ui-03;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.toggleContactDetailsButton {
|
|
69
|
+
> svg {
|
|
70
|
+
fill: $color-blue-60-2 !important;
|
|
71
|
+
}
|
|
72
|
+
}
|