@openmrs/esm-form-engine-lib 4.2.2-pre.2464 → 4.2.2-pre.2470
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/components/inputs/ui-select-extended/ui-select-extended.component.js +21 -7
- package/dist/components/inputs/ui-select-extended/ui-select-extended.scss +24 -0
- package/dist/datasources/concept-data-source.js +27 -3
- package/dist/src/components/inputs/ui-select-extended/ui-select-extended.component.d.ts +4 -0
- package/dist/src/components/inputs/ui-select-extended/ui-select-extended.component.d.ts.map +1 -1
- package/dist/src/datasources/concept-data-source.d.ts +20 -0
- package/dist/src/datasources/concept-data-source.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +2 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/transformers/default-schema-transformer.js +5 -1
- package/package.json +1 -1
- package/src/components/inputs/ui-select-extended/ui-select-extended.component.tsx +18 -7
- package/src/components/inputs/ui-select-extended/ui-select-extended.scss +24 -0
- package/src/datasources/concept-data-source.ts +58 -4
- package/src/transformers/default-schema-transformer.ts +5 -1
- package/src/types/index.ts +2 -2
|
@@ -48,12 +48,15 @@ const UiSelectExtended = ({ field, errors, warnings, setFieldValue })=>{
|
|
|
48
48
|
items,
|
|
49
49
|
value
|
|
50
50
|
]);
|
|
51
|
+
const hasCodes = useMemo(()=>items.some((item)=>item.code), [
|
|
52
|
+
items
|
|
53
|
+
]);
|
|
51
54
|
const debouncedSearch = debounce((searchTerm, dataSource)=>{
|
|
52
55
|
setIsSearching(true);
|
|
53
56
|
dataSource.fetchData(searchTerm, config).then((dataItems)=>{
|
|
54
57
|
if (dataItems.length) {
|
|
55
58
|
const currentSelectedItem = items.find((item)=>item.uuid == value);
|
|
56
|
-
const newItems = dataItems.map(dataSource.toUuidAndDisplay);
|
|
59
|
+
const newItems = dataItems.map((item)=>dataSource.toUuidAndDisplay(item, config));
|
|
57
60
|
if (currentSelectedItem && !newItems.some((item)=>item.uuid == currentSelectedItem.uuid)) {
|
|
58
61
|
newItems.unshift(currentSelectedItem);
|
|
59
62
|
}
|
|
@@ -88,7 +91,7 @@ const UiSelectExtended = ({ field, errors, warnings, setFieldValue })=>{
|
|
|
88
91
|
referencedValue: dataSourceDependentValue
|
|
89
92
|
}).then((dataItems)=>{
|
|
90
93
|
if (!ignore) {
|
|
91
|
-
setItems(dataItems.map(dataSource.toUuidAndDisplay));
|
|
94
|
+
setItems(dataItems.map((item)=>dataSource.toUuidAndDisplay(item, config)));
|
|
92
95
|
setIsLoading(false);
|
|
93
96
|
}
|
|
94
97
|
}).catch((err)=>{
|
|
@@ -121,10 +124,10 @@ const UiSelectExtended = ({ field, errors, warnings, setFieldValue })=>{
|
|
|
121
124
|
if (value && !isDirty && dataSource && isSearchable && !items.length) {
|
|
122
125
|
// For search-based instances, fetch the initial item to resolve its display property
|
|
123
126
|
setIsLoading(true);
|
|
124
|
-
dataSource.fetchSingleItem(value).then((item)=>{
|
|
127
|
+
dataSource.fetchSingleItem(value, config).then((item)=>{
|
|
125
128
|
if (!ignore) {
|
|
126
129
|
setItems([
|
|
127
|
-
dataSource.toUuidAndDisplay(item)
|
|
130
|
+
dataSource.toUuidAndDisplay(item, config)
|
|
128
131
|
]);
|
|
129
132
|
setIsLoading(false);
|
|
130
133
|
}
|
|
@@ -143,14 +146,15 @@ const UiSelectExtended = ({ field, errors, warnings, setFieldValue })=>{
|
|
|
143
146
|
isDirty,
|
|
144
147
|
dataSource,
|
|
145
148
|
isSearchable,
|
|
146
|
-
items
|
|
149
|
+
items,
|
|
150
|
+
config
|
|
147
151
|
]);
|
|
148
152
|
if (isLoading) {
|
|
149
153
|
return /*#__PURE__*/ React.createElement(DropdownSkeleton, null);
|
|
150
154
|
}
|
|
151
155
|
return isViewMode(sessionMode) || isTrue(field.readonly) ? /*#__PURE__*/ React.createElement(FieldValueView, {
|
|
152
156
|
label: t(field.label),
|
|
153
|
-
value: value ? items.find((item)=>item.uuid == value)
|
|
157
|
+
value: value ? getItemText(items.find((item)=>item.uuid == value)) : value,
|
|
154
158
|
conceptName: field.meta?.concept?.display,
|
|
155
159
|
isInline: isInline
|
|
156
160
|
}) : !field.isHidden && /*#__PURE__*/ React.createElement("div", {
|
|
@@ -161,7 +165,14 @@ const UiSelectExtended = ({ field, errors, warnings, setFieldValue })=>{
|
|
|
161
165
|
field: field
|
|
162
166
|
}),
|
|
163
167
|
items: items,
|
|
164
|
-
|
|
168
|
+
itemToElement: (item)=>/*#__PURE__*/ React.createElement("span", {
|
|
169
|
+
className: styles.item
|
|
170
|
+
}, hasCodes && /*#__PURE__*/ React.createElement("span", {
|
|
171
|
+
className: styles.code
|
|
172
|
+
}, item?.code), /*#__PURE__*/ React.createElement("span", {
|
|
173
|
+
className: styles.label
|
|
174
|
+
}, item?.display)),
|
|
175
|
+
itemToString: getItemText,
|
|
165
176
|
selectedItem: selectedItem,
|
|
166
177
|
placeholder: isSearchable ? t('search', 'Search') + '...' : null,
|
|
167
178
|
onChange: ({ selectedItem })=>{
|
|
@@ -198,3 +209,6 @@ const UiSelectExtended = ({ field, errors, warnings, setFieldValue })=>{
|
|
|
198
209
|
})));
|
|
199
210
|
};
|
|
200
211
|
export default UiSelectExtended;
|
|
212
|
+
export function getItemText(item) {
|
|
213
|
+
return item?.code ? `${item.code} ${item.display}` : item?.display ?? '';
|
|
214
|
+
}
|
|
@@ -17,3 +17,27 @@
|
|
|
17
17
|
.loader {
|
|
18
18
|
padding: 0.25rem 0.5rem;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
.item {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: 0.5rem;
|
|
25
|
+
min-width: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.code {
|
|
29
|
+
flex: none;
|
|
30
|
+
min-width: 4rem;
|
|
31
|
+
max-width: 8rem;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
color: inherit;
|
|
34
|
+
font-family: 'IBM Plex Mono', 'Menlo', 'Consolas', monospace;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
text-overflow: ellipsis;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.label {
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
text-overflow: ellipsis;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { openmrsFetch, restBaseUrl } from "@openmrs/esm-framework";
|
|
2
2
|
import { BaseOpenMRSDataSource } from "./data-source.js";
|
|
3
3
|
import { isEmpty } from "../validators/form-validator.js";
|
|
4
|
+
const baseConceptRepresentation = 'custom:(uuid,display,conceptClass:(uuid,display))';
|
|
5
|
+
const codedConceptRepresentation = 'custom:(uuid,display,conceptClass:(uuid,display),mappings:(conceptMapType:(uuid,display),conceptReferenceTerm:(code,conceptSource:(uuid))))';
|
|
6
|
+
const sameAsConceptMapTypeUuid = '35543629-7d8c-11e1-909d-c80aa9edcf4e';
|
|
4
7
|
export class ConceptDataSource extends BaseOpenMRSDataSource {
|
|
5
8
|
fetchData(searchTerm, config) {
|
|
6
9
|
if (isEmpty(config?.class) && isEmpty(config?.concept) && !config?.useSetMembersByConcept && isEmpty(searchTerm)) {
|
|
7
10
|
return Promise.resolve([]);
|
|
8
11
|
}
|
|
9
|
-
|
|
12
|
+
const representation = config?.conceptSourceUuid ? codedConceptRepresentation : baseConceptRepresentation;
|
|
13
|
+
let searchUrl = `${restBaseUrl}/concept?name=&searchType=fuzzy&v=${representation}`;
|
|
10
14
|
if (config?.class) {
|
|
11
15
|
if (typeof config.class == 'string') {
|
|
12
16
|
const urlParts = searchUrl.split('searchType=fuzzy');
|
|
@@ -19,7 +23,8 @@ export class ConceptDataSource extends BaseOpenMRSDataSource {
|
|
|
19
23
|
}
|
|
20
24
|
if (config?.concept && config?.useSetMembersByConcept) {
|
|
21
25
|
let urlParts = searchUrl.split('?name=&searchType=fuzzy&v=');
|
|
22
|
-
|
|
26
|
+
const setMemberRepresentation = config?.conceptSourceUuid ? 'custom:(uuid,setMembers:(uuid,display,mappings:(conceptMapType:(uuid,display),conceptReferenceTerm:(code,conceptSource:(uuid)))))' : 'custom:(uuid,setMembers:(uuid,display))';
|
|
27
|
+
searchUrl = `${urlParts[0]}/${config.concept}?v=${setMemberRepresentation}`;
|
|
23
28
|
return openmrsFetch(searchTerm ? `${searchUrl}&q=${searchTerm}` : searchUrl).then(({ data })=>{
|
|
24
29
|
// return the setMembers from the retrieved concept object
|
|
25
30
|
return data['setMembers'];
|
|
@@ -29,7 +34,26 @@ export class ConceptDataSource extends BaseOpenMRSDataSource {
|
|
|
29
34
|
return data.results;
|
|
30
35
|
});
|
|
31
36
|
}
|
|
37
|
+
fetchSingleItem(uuid, config) {
|
|
38
|
+
const representation = config?.conceptSourceUuid ? codedConceptRepresentation : baseConceptRepresentation;
|
|
39
|
+
return openmrsFetch(`${restBaseUrl}/concept/${uuid}?v=${representation}`).then(({ data })=>data);
|
|
40
|
+
}
|
|
41
|
+
toUuidAndDisplay(data, config) {
|
|
42
|
+
const item = super.toUuidAndDisplay(data);
|
|
43
|
+
const code = this.getConceptSourceMapping(data, config?.conceptSourceUuid)?.conceptReferenceTerm?.code;
|
|
44
|
+
return code ? {
|
|
45
|
+
...item,
|
|
46
|
+
code
|
|
47
|
+
} : item;
|
|
48
|
+
}
|
|
49
|
+
getConceptSourceMapping(concept, conceptSourceUuid) {
|
|
50
|
+
if (!conceptSourceUuid) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const mappingsToSource = concept.mappings?.filter((mapping)=>mapping.conceptReferenceTerm?.conceptSource?.uuid === conceptSourceUuid) ?? [];
|
|
54
|
+
return mappingsToSource.find((mapping)=>mapping.conceptMapType?.uuid === sameAsConceptMapTypeUuid || mapping.conceptMapType?.display?.toUpperCase() === 'SAME-AS') ?? mappingsToSource[0];
|
|
55
|
+
}
|
|
32
56
|
constructor(){
|
|
33
|
-
super(`${restBaseUrl}/concept?v
|
|
57
|
+
super(`${restBaseUrl}/concept?v=${baseConceptRepresentation}`);
|
|
34
58
|
}
|
|
35
59
|
}
|
|
@@ -2,4 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import { type FormFieldInputProps } from '../../../types';
|
|
3
3
|
declare const UiSelectExtended: React.FC<FormFieldInputProps>;
|
|
4
4
|
export default UiSelectExtended;
|
|
5
|
+
export declare function getItemText(item?: {
|
|
6
|
+
code?: string;
|
|
7
|
+
display?: string;
|
|
8
|
+
}): string;
|
|
5
9
|
//# sourceMappingURL=ui-select-extended.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-select-extended.component.d.ts","sourceRoot":"","sources":["../../../../../src/components/inputs/ui-select-extended/ui-select-extended.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAYjF,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAO3E,QAAA,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,
|
|
1
|
+
{"version":3,"file":"ui-select-extended.component.d.ts","sourceRoot":"","sources":["../../../../../src/components/inputs/ui-select-extended/ui-select-extended.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAYjF,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAO3E,QAAA,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAkMnD,CAAC;AAEF,eAAe,gBAAgB,CAAC;AAEhC,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE9E"}
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
|
1
2
|
import { BaseOpenMRSDataSource } from './data-source';
|
|
3
|
+
interface ConceptMapping {
|
|
4
|
+
conceptMapType?: {
|
|
5
|
+
uuid?: string;
|
|
6
|
+
display?: string;
|
|
7
|
+
};
|
|
8
|
+
conceptReferenceTerm?: {
|
|
9
|
+
code?: string;
|
|
10
|
+
conceptSource?: {
|
|
11
|
+
uuid?: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
interface ConceptResource extends OpenmrsResource {
|
|
16
|
+
mappings?: Array<ConceptMapping>;
|
|
17
|
+
}
|
|
2
18
|
export declare class ConceptDataSource extends BaseOpenMRSDataSource {
|
|
3
19
|
constructor();
|
|
4
20
|
fetchData(searchTerm: string, config?: Record<string, any>): Promise<any[]>;
|
|
21
|
+
fetchSingleItem(uuid: string, config?: Record<string, any>): Promise<ConceptResource | null>;
|
|
22
|
+
toUuidAndDisplay(data: ConceptResource, config?: Record<string, any>): OpenmrsResource;
|
|
23
|
+
private getConceptSourceMapping;
|
|
5
24
|
}
|
|
25
|
+
export {};
|
|
6
26
|
//# sourceMappingURL=concept-data-source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"concept-data-source.d.ts","sourceRoot":"","sources":["../../../src/datasources/concept-data-source.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"concept-data-source.d.ts","sourceRoot":"","sources":["../../../src/datasources/concept-data-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAQtD,UAAU,cAAc;IACtB,cAAc,CAAC,EAAE;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,oBAAoB,CAAC,EAAE;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE;YACd,IAAI,CAAC,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;CACH;AAED,UAAU,eAAgB,SAAQ,eAAe;IAC/C,QAAQ,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;CAClC;AAED,qBAAa,iBAAkB,SAAQ,qBAAqB;;IAK1D,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAqC3E,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAK5F,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,eAAe;IAMtF,OAAO,CAAC,uBAAuB;CAgBhC"}
|
|
@@ -61,11 +61,11 @@ export interface DataSource<T> {
|
|
|
61
61
|
* Fetches a single item from the data source based on its UUID.
|
|
62
62
|
* This is used for value binding with previously selected values.
|
|
63
63
|
*/
|
|
64
|
-
fetchSingleItem(uuid: string): Promise<T | null>;
|
|
64
|
+
fetchSingleItem(uuid: string, config?: Record<string, any>): Promise<T | null>;
|
|
65
65
|
/**
|
|
66
66
|
* Maps a data source item to an object with a uuid and display property
|
|
67
67
|
*/
|
|
68
|
-
toUuidAndDisplay(item: T): OpenmrsResource;
|
|
68
|
+
toUuidAndDisplay(item: T, config?: Record<string, any>): OpenmrsResource;
|
|
69
69
|
}
|
|
70
70
|
export interface ControlTemplate {
|
|
71
71
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC3F,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC;AAEtE,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC;IACtB,QAAQ,EAAE,UAAU,CAAC;IACrB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,IAAI,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,eAAe,EAAE,eAAe,CAAC;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,iBAAiB,CAAC,EAAE,eAAe,CAAC;IACpC,yBAAyB,CAAC,EAAE,eAAe,CAAC;IAC5C,SAAS,EAAE,aAAa,CAAC;IACzB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAC1D,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACzD,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,GAAG,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB,KAAK,GAAG,CAAC;IACtF;;;;OAIG;IACH,eAAe,EAAE,CACf,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,eAAe,EAC7B,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACxB;;OAEG;IACH,gBAAgB,EAAE,CAChB,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,eAAe,EAC7B,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IAChD;;OAEG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;IACvD;;OAEG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B;;OAEG;IACH,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhF;;;OAGG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -265,10 +265,12 @@ function handleQuestionsWithObsComments(sectionQuestions) {
|
|
|
265
265
|
}
|
|
266
266
|
function handleDiagnosis(question) {
|
|
267
267
|
if ('dataSource' in question.questionOptions && question.questionOptions['dataSource'] === 'diagnoses' || question.type === 'diagnosis') {
|
|
268
|
+
const datasourceConfig = question.questionOptions.datasource?.config;
|
|
268
269
|
question.questionOptions.datasource = {
|
|
269
270
|
name: 'problem_datasource',
|
|
270
271
|
config: {
|
|
271
|
-
|
|
272
|
+
...datasourceConfig,
|
|
273
|
+
class: question.questionOptions.diagnosis?.conceptClasses ?? datasourceConfig?.class
|
|
272
274
|
}
|
|
273
275
|
};
|
|
274
276
|
if (question.questionOptions.diagnosis?.conceptSet) {
|
|
@@ -278,6 +280,8 @@ function handleDiagnosis(question) {
|
|
|
278
280
|
datasource: {
|
|
279
281
|
name: 'problem_datasource',
|
|
280
282
|
config: {
|
|
283
|
+
...datasourceConfig,
|
|
284
|
+
concept: question.questionOptions.diagnosis.conceptSet,
|
|
281
285
|
useSetMembersByConcept: true
|
|
282
286
|
}
|
|
283
287
|
}
|
package/package.json
CHANGED
|
@@ -46,6 +46,7 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
|
|
|
46
46
|
}, [sessionMode, field.readonly, field.inlineRendering, layoutType, workspaceLayout]);
|
|
47
47
|
|
|
48
48
|
const selectedItem = useMemo(() => items.find((item) => item.uuid == value) || null, [items, value]);
|
|
49
|
+
const hasCodes = useMemo(() => items.some((item) => item.code), [items]);
|
|
49
50
|
|
|
50
51
|
const debouncedSearch = debounce((searchTerm: string, dataSource: DataSource<OpenmrsResource>) => {
|
|
51
52
|
setIsSearching(true);
|
|
@@ -55,7 +56,7 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
|
|
|
55
56
|
.then((dataItems) => {
|
|
56
57
|
if (dataItems.length) {
|
|
57
58
|
const currentSelectedItem = items.find((item) => item.uuid == value);
|
|
58
|
-
const newItems = dataItems.map(dataSource.toUuidAndDisplay);
|
|
59
|
+
const newItems = dataItems.map((item) => dataSource.toUuidAndDisplay(item, config));
|
|
59
60
|
if (currentSelectedItem && !newItems.some((item) => item.uuid == currentSelectedItem.uuid)) {
|
|
60
61
|
newItems.unshift(currentSelectedItem);
|
|
61
62
|
}
|
|
@@ -98,7 +99,7 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
|
|
|
98
99
|
.fetchData(null, { ...config, referencedValue: dataSourceDependentValue })
|
|
99
100
|
.then((dataItems) => {
|
|
100
101
|
if (!ignore) {
|
|
101
|
-
setItems(dataItems.map(dataSource.toUuidAndDisplay));
|
|
102
|
+
setItems(dataItems.map((item) => dataSource.toUuidAndDisplay(item, config)));
|
|
102
103
|
setIsLoading(false);
|
|
103
104
|
}
|
|
104
105
|
})
|
|
@@ -128,10 +129,10 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
|
|
|
128
129
|
// For search-based instances, fetch the initial item to resolve its display property
|
|
129
130
|
setIsLoading(true);
|
|
130
131
|
dataSource
|
|
131
|
-
.fetchSingleItem(value)
|
|
132
|
+
.fetchSingleItem(value, config)
|
|
132
133
|
.then((item) => {
|
|
133
134
|
if (!ignore) {
|
|
134
|
-
setItems([dataSource.toUuidAndDisplay(item)]);
|
|
135
|
+
setItems([dataSource.toUuidAndDisplay(item, config)]);
|
|
135
136
|
setIsLoading(false);
|
|
136
137
|
}
|
|
137
138
|
})
|
|
@@ -146,7 +147,7 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
|
|
|
146
147
|
return () => {
|
|
147
148
|
ignore = true;
|
|
148
149
|
};
|
|
149
|
-
}, [value, isDirty, dataSource, isSearchable, items]);
|
|
150
|
+
}, [value, isDirty, dataSource, isSearchable, items, config]);
|
|
150
151
|
|
|
151
152
|
if (isLoading) {
|
|
152
153
|
return <DropdownSkeleton />;
|
|
@@ -155,7 +156,7 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
|
|
|
155
156
|
return isViewMode(sessionMode) || isTrue(field.readonly) ? (
|
|
156
157
|
<FieldValueView
|
|
157
158
|
label={t(field.label)}
|
|
158
|
-
value={value ? items.find((item) => item.uuid == value)
|
|
159
|
+
value={value ? getItemText(items.find((item) => item.uuid == value)) : value}
|
|
159
160
|
conceptName={field.meta?.concept?.display}
|
|
160
161
|
isInline={isInline}
|
|
161
162
|
/>
|
|
@@ -167,7 +168,13 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
|
|
|
167
168
|
id={field.id}
|
|
168
169
|
titleText={<FieldLabel field={field} />}
|
|
169
170
|
items={items}
|
|
170
|
-
|
|
171
|
+
itemToElement={(item) => (
|
|
172
|
+
<span className={styles.item}>
|
|
173
|
+
{hasCodes && <span className={styles.code}>{item?.code}</span>}
|
|
174
|
+
<span className={styles.label}>{item?.display}</span>
|
|
175
|
+
</span>
|
|
176
|
+
)}
|
|
177
|
+
itemToString={getItemText}
|
|
171
178
|
selectedItem={selectedItem}
|
|
172
179
|
placeholder={isSearchable ? t('search', 'Search') + '...' : null}
|
|
173
180
|
onChange={({ selectedItem }) => {
|
|
@@ -207,3 +214,7 @@ const UiSelectExtended: React.FC<FormFieldInputProps> = ({ field, errors, warnin
|
|
|
207
214
|
};
|
|
208
215
|
|
|
209
216
|
export default UiSelectExtended;
|
|
217
|
+
|
|
218
|
+
export function getItemText(item?: { code?: string; display?: string }): string {
|
|
219
|
+
return item?.code ? `${item.code} ${item.display}` : item?.display ?? '';
|
|
220
|
+
}
|
|
@@ -17,3 +17,27 @@
|
|
|
17
17
|
.loader {
|
|
18
18
|
padding: 0.25rem 0.5rem;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
.item {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: 0.5rem;
|
|
25
|
+
min-width: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.code {
|
|
29
|
+
flex: none;
|
|
30
|
+
min-width: 4rem;
|
|
31
|
+
max-width: 8rem;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
color: inherit;
|
|
34
|
+
font-family: 'IBM Plex Mono', 'Menlo', 'Consolas', monospace;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
text-overflow: ellipsis;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.label {
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
text-overflow: ellipsis;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
}
|
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
1
|
+
import { openmrsFetch, restBaseUrl, type OpenmrsResource } from '@openmrs/esm-framework';
|
|
2
2
|
import { BaseOpenMRSDataSource } from './data-source';
|
|
3
3
|
import { isEmpty } from '../validators/form-validator';
|
|
4
4
|
|
|
5
|
+
const baseConceptRepresentation = 'custom:(uuid,display,conceptClass:(uuid,display))';
|
|
6
|
+
const codedConceptRepresentation =
|
|
7
|
+
'custom:(uuid,display,conceptClass:(uuid,display),mappings:(conceptMapType:(uuid,display),conceptReferenceTerm:(code,conceptSource:(uuid))))';
|
|
8
|
+
const sameAsConceptMapTypeUuid = '35543629-7d8c-11e1-909d-c80aa9edcf4e';
|
|
9
|
+
|
|
10
|
+
interface ConceptMapping {
|
|
11
|
+
conceptMapType?: {
|
|
12
|
+
uuid?: string;
|
|
13
|
+
display?: string;
|
|
14
|
+
};
|
|
15
|
+
conceptReferenceTerm?: {
|
|
16
|
+
code?: string;
|
|
17
|
+
conceptSource?: {
|
|
18
|
+
uuid?: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ConceptResource extends OpenmrsResource {
|
|
24
|
+
mappings?: Array<ConceptMapping>;
|
|
25
|
+
}
|
|
26
|
+
|
|
5
27
|
export class ConceptDataSource extends BaseOpenMRSDataSource {
|
|
6
28
|
constructor() {
|
|
7
|
-
super(`${restBaseUrl}/concept?v
|
|
29
|
+
super(`${restBaseUrl}/concept?v=${baseConceptRepresentation}`);
|
|
8
30
|
}
|
|
9
31
|
|
|
10
32
|
fetchData(searchTerm: string, config?: Record<string, any>): Promise<any[]> {
|
|
@@ -12,7 +34,8 @@ export class ConceptDataSource extends BaseOpenMRSDataSource {
|
|
|
12
34
|
return Promise.resolve([]);
|
|
13
35
|
}
|
|
14
36
|
|
|
15
|
-
|
|
37
|
+
const representation = config?.conceptSourceUuid ? codedConceptRepresentation : baseConceptRepresentation;
|
|
38
|
+
let searchUrl = `${restBaseUrl}/concept?name=&searchType=fuzzy&v=${representation}`;
|
|
16
39
|
if (config?.class) {
|
|
17
40
|
if (typeof config.class == 'string') {
|
|
18
41
|
const urlParts = searchUrl.split('searchType=fuzzy');
|
|
@@ -28,7 +51,10 @@ export class ConceptDataSource extends BaseOpenMRSDataSource {
|
|
|
28
51
|
|
|
29
52
|
if (config?.concept && config?.useSetMembersByConcept) {
|
|
30
53
|
let urlParts = searchUrl.split('?name=&searchType=fuzzy&v=');
|
|
31
|
-
|
|
54
|
+
const setMemberRepresentation = config?.conceptSourceUuid
|
|
55
|
+
? 'custom:(uuid,setMembers:(uuid,display,mappings:(conceptMapType:(uuid,display),conceptReferenceTerm:(code,conceptSource:(uuid)))))'
|
|
56
|
+
: 'custom:(uuid,setMembers:(uuid,display))';
|
|
57
|
+
searchUrl = `${urlParts[0]}/${config.concept}?v=${setMemberRepresentation}`;
|
|
32
58
|
return openmrsFetch(searchTerm ? `${searchUrl}&q=${searchTerm}` : searchUrl).then(({ data }) => {
|
|
33
59
|
// return the setMembers from the retrieved concept object
|
|
34
60
|
return data['setMembers'];
|
|
@@ -39,4 +65,32 @@ export class ConceptDataSource extends BaseOpenMRSDataSource {
|
|
|
39
65
|
return data.results;
|
|
40
66
|
});
|
|
41
67
|
}
|
|
68
|
+
|
|
69
|
+
fetchSingleItem(uuid: string, config?: Record<string, any>): Promise<ConceptResource | null> {
|
|
70
|
+
const representation = config?.conceptSourceUuid ? codedConceptRepresentation : baseConceptRepresentation;
|
|
71
|
+
return openmrsFetch(`${restBaseUrl}/concept/${uuid}?v=${representation}`).then(({ data }) => data);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
toUuidAndDisplay(data: ConceptResource, config?: Record<string, any>): OpenmrsResource {
|
|
75
|
+
const item = super.toUuidAndDisplay(data);
|
|
76
|
+
const code = this.getConceptSourceMapping(data, config?.conceptSourceUuid)?.conceptReferenceTerm?.code;
|
|
77
|
+
return code ? { ...item, code } : item;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private getConceptSourceMapping(concept: ConceptResource, conceptSourceUuid?: string): ConceptMapping | undefined {
|
|
81
|
+
if (!conceptSourceUuid) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const mappingsToSource =
|
|
86
|
+
concept.mappings?.filter((mapping) => mapping.conceptReferenceTerm?.conceptSource?.uuid === conceptSourceUuid) ??
|
|
87
|
+
[];
|
|
88
|
+
return (
|
|
89
|
+
mappingsToSource.find(
|
|
90
|
+
(mapping) =>
|
|
91
|
+
mapping.conceptMapType?.uuid === sameAsConceptMapTypeUuid ||
|
|
92
|
+
mapping.conceptMapType?.display?.toUpperCase() === 'SAME-AS',
|
|
93
|
+
) ?? mappingsToSource[0]
|
|
94
|
+
);
|
|
95
|
+
}
|
|
42
96
|
}
|
|
@@ -295,10 +295,12 @@ function handleDiagnosis(question: FormField) {
|
|
|
295
295
|
('dataSource' in question.questionOptions && question.questionOptions['dataSource'] === 'diagnoses') ||
|
|
296
296
|
question.type === 'diagnosis'
|
|
297
297
|
) {
|
|
298
|
+
const datasourceConfig = question.questionOptions.datasource?.config;
|
|
298
299
|
question.questionOptions.datasource = {
|
|
299
300
|
name: 'problem_datasource',
|
|
300
301
|
config: {
|
|
301
|
-
|
|
302
|
+
...datasourceConfig,
|
|
303
|
+
class: question.questionOptions.diagnosis?.conceptClasses ?? datasourceConfig?.class,
|
|
302
304
|
},
|
|
303
305
|
};
|
|
304
306
|
if (question.questionOptions.diagnosis?.conceptSet) {
|
|
@@ -308,6 +310,8 @@ function handleDiagnosis(question: FormField) {
|
|
|
308
310
|
datasource: {
|
|
309
311
|
name: 'problem_datasource',
|
|
310
312
|
config: {
|
|
313
|
+
...datasourceConfig,
|
|
314
|
+
concept: question.questionOptions.diagnosis.conceptSet,
|
|
311
315
|
useSetMembersByConcept: true,
|
|
312
316
|
},
|
|
313
317
|
},
|
package/src/types/index.ts
CHANGED
|
@@ -75,12 +75,12 @@ export interface DataSource<T> {
|
|
|
75
75
|
* Fetches a single item from the data source based on its UUID.
|
|
76
76
|
* This is used for value binding with previously selected values.
|
|
77
77
|
*/
|
|
78
|
-
fetchSingleItem(uuid: string): Promise<T | null>;
|
|
78
|
+
fetchSingleItem(uuid: string, config?: Record<string, any>): Promise<T | null>;
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Maps a data source item to an object with a uuid and display property
|
|
82
82
|
*/
|
|
83
|
-
toUuidAndDisplay(item: T): OpenmrsResource;
|
|
83
|
+
toUuidAndDisplay(item: T, config?: Record<string, any>): OpenmrsResource;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
export interface ControlTemplate {
|