@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.
@@ -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)?.display : 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
- itemToString: (item)=>item?.display,
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
- let searchUrl = `${restBaseUrl}/concept?name=&searchType=fuzzy&v=custom:(uuid,display,conceptClass:(uuid,display))`;
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
- searchUrl = `${urlParts[0]}/${config.concept}?v=custom:(uuid,setMembers:(uuid,display))`;
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=custom:(uuid,display,conceptClass:(uuid,display))`);
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,CA2LnD,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
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":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,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;CAgC5E"}
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":"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"}
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
- class: question.questionOptions.diagnosis?.conceptClasses
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-form-engine-lib",
3
- "version": "4.2.2-pre.2464",
3
+ "version": "4.2.2-pre.2470",
4
4
  "description": "React Form Engine for O3",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -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)?.display : 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
- itemToString={(item) => item?.display}
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=custom:(uuid,display,conceptClass:(uuid,display))`);
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
- let searchUrl = `${restBaseUrl}/concept?name=&searchType=fuzzy&v=custom:(uuid,display,conceptClass:(uuid,display))`;
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
- searchUrl = `${urlParts[0]}/${config.concept}?v=custom:(uuid,setMembers:(uuid,display))`;
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
- class: question.questionOptions.diagnosis?.conceptClasses,
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
  },
@@ -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 {