@openmrs/esm-form-builder-app 2.0.2-pre.574 → 2.0.2-pre.586

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +43 -17
  2. package/dist/127.js +1 -1
  3. package/dist/127.js.map +1 -1
  4. package/dist/150.js +1 -1
  5. package/dist/150.js.map +1 -1
  6. package/dist/153.js +1 -1
  7. package/dist/153.js.map +1 -1
  8. package/dist/164.js +1 -1
  9. package/dist/256.js +1 -1
  10. package/dist/319.js +1 -1
  11. package/dist/447.js +1 -1
  12. package/dist/447.js.map +1 -1
  13. package/dist/515.js +2 -0
  14. package/dist/{773.js.LICENSE.txt → 515.js.LICENSE.txt} +9 -0
  15. package/dist/515.js.map +1 -0
  16. package/dist/527.js +1 -0
  17. package/dist/527.js.map +1 -0
  18. package/dist/574.js +1 -1
  19. package/dist/757.js +1 -1
  20. package/dist/788.js +1 -1
  21. package/dist/800.js +1 -1
  22. package/dist/800.js.map +1 -1
  23. package/dist/807.js +1 -1
  24. package/dist/833.js +1 -1
  25. package/dist/878.js +2 -0
  26. package/dist/{208.js.LICENSE.txt → 878.js.LICENSE.txt} +2 -1
  27. package/dist/878.js.map +1 -0
  28. package/dist/main.js +1 -1
  29. package/dist/main.js.map +1 -1
  30. package/dist/openmrs-esm-form-builder-app.js +1 -1
  31. package/dist/openmrs-esm-form-builder-app.js.buildmanifest.json +154 -175
  32. package/dist/openmrs-esm-form-builder-app.js.map +1 -1
  33. package/dist/routes.json +1 -1
  34. package/package.json +35 -32
  35. package/src/components/action-buttons/action-buttons.component.tsx +65 -101
  36. package/src/components/dashboard/dashboard.component.tsx +98 -174
  37. package/src/components/dashboard/dashboard.test.tsx +51 -81
  38. package/src/components/empty-state/empty-data-illustration.component.tsx +4 -16
  39. package/src/components/empty-state/empty-state.component.tsx +11 -15
  40. package/src/components/error-state/error-state.component.tsx +11 -13
  41. package/src/components/form-editor/form-editor.component.tsx +97 -128
  42. package/src/components/form-renderer/form-renderer.component.tsx +30 -41
  43. package/src/components/interactive-builder/add-question-modal.component.tsx +129 -167
  44. package/src/components/interactive-builder/delete-page-modal.component.tsx +24 -37
  45. package/src/components/interactive-builder/delete-question-modal.component.tsx +25 -47
  46. package/src/components/interactive-builder/delete-section-modal.component.tsx +24 -37
  47. package/src/components/interactive-builder/draggable-question.component.tsx +21 -34
  48. package/src/components/interactive-builder/droppable-container.component.tsx +5 -5
  49. package/src/components/interactive-builder/edit-question-modal.component.tsx +191 -233
  50. package/src/components/interactive-builder/editable-value.component.tsx +12 -17
  51. package/src/components/interactive-builder/interactive-builder.component.tsx +134 -184
  52. package/src/components/interactive-builder/new-form-modal.component.tsx +35 -49
  53. package/src/components/interactive-builder/page-modal.component.tsx +29 -45
  54. package/src/components/interactive-builder/question-modal.scss +7 -0
  55. package/src/components/interactive-builder/section-modal.component.tsx +29 -40
  56. package/src/components/interactive-builder/value-editor.component.tsx +11 -16
  57. package/src/components/modals/save-form-modal.component.tsx +112 -165
  58. package/src/components/pagination/index.ts +2 -2
  59. package/src/components/pagination/pagination.component.tsx +8 -13
  60. package/src/components/pagination/usePaginationInfo.ts +4 -9
  61. package/src/components/schema-editor/schema-editor.component.tsx +11 -17
  62. package/src/config-schema.ts +28 -30
  63. package/src/declarations.d.ts +4 -3
  64. package/src/form-builder-admin-card-link.component.tsx +7 -11
  65. package/src/forms.resource.ts +66 -87
  66. package/src/hooks/useClobdata.ts +10 -12
  67. package/src/hooks/useConceptLookup.ts +5 -8
  68. package/src/hooks/useConceptName.ts +6 -9
  69. package/src/hooks/useEncounterTypes.ts +8 -8
  70. package/src/hooks/useForm.ts +7 -7
  71. package/src/hooks/useForms.ts +5 -8
  72. package/src/index.ts +11 -23
  73. package/src/root.component.tsx +4 -4
  74. package/src/setup-tests.ts +1 -9
  75. package/src/test-helpers.tsx +8 -15
  76. package/src/types.ts +16 -8
  77. package/dist/208.js +0 -2
  78. package/dist/208.js.map +0 -1
  79. package/dist/536.js +0 -1
  80. package/dist/536.js.map +0 -1
  81. package/dist/62.js +0 -1
  82. package/dist/62.js.map +0 -1
  83. package/dist/773.js +0 -2
  84. package/dist/773.js.map +0 -1
  85. package/src/constants.ts +0 -3
@@ -1,13 +1,16 @@
1
- import React, { useMemo, useState } from "react";
2
- import { useTranslation } from "react-i18next";
1
+ import React, { useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import debounce from 'lodash-es/debounce';
4
+ import flattenDeep from 'lodash-es/flattenDeep';
3
5
  import {
4
6
  Button,
5
7
  ComposedModal,
6
8
  Form,
7
9
  FormGroup,
8
10
  FormLabel,
9
- Layer,
10
11
  InlineLoading,
12
+ InlineNotification,
13
+ Layer,
11
14
  ModalBody,
12
15
  ModalFooter,
13
16
  ModalHeader,
@@ -21,19 +24,17 @@ import {
21
24
  Tag,
22
25
  TextInput,
23
26
  Tile,
24
- } from "@carbon/react";
25
- import { ArrowUpRight } from "@carbon/react/icons";
26
- import debounce from "lodash-es/debounce";
27
- import flattenDeep from "lodash-es/flattenDeep";
28
- import { showNotification, showToast, useConfig } from "@openmrs/esm-framework";
29
- import type { RenderType } from "@openmrs/openmrs-form-engine-lib";
30
-
31
- import type { Concept, ConceptMapping, Question, Schema } from "../../types";
32
- import { useConceptLookup } from "../../hooks/useConceptLookup";
33
- import { useConceptName } from "../../hooks/useConceptName";
34
- import styles from "./question-modal.scss";
35
-
36
- type EditQuestionModalProps = {
27
+ } from '@carbon/react';
28
+ import { ArrowUpRight } from '@carbon/react/icons';
29
+ import { showNotification, showToast, useConfig } from '@openmrs/esm-framework';
30
+ import type { RenderType } from '@openmrs/openmrs-form-engine-lib';
31
+
32
+ import type { Concept, ConceptMapping, Question, QuestionType, Schema } from '../../types';
33
+ import { useConceptLookup } from '../../hooks/useConceptLookup';
34
+ import { useConceptName } from '../../hooks/useConceptName';
35
+ import styles from './question-modal.scss';
36
+
37
+ interface EditQuestionModalProps {
37
38
  onModalChange: (showModal: boolean) => void;
38
39
  onQuestionEdit: (question: Question) => void;
39
40
  onSchemaChange: (schema: Schema) => void;
@@ -44,7 +45,17 @@ type EditQuestionModalProps = {
44
45
  schema: Schema;
45
46
  sectionIndex: number;
46
47
  showModal: boolean;
47
- };
48
+ }
49
+
50
+ interface Config {
51
+ fieldTypes: Array<RenderType>;
52
+ questionTypes: Array<QuestionType>;
53
+ }
54
+
55
+ interface Item {
56
+ id: string;
57
+ text: string;
58
+ }
48
59
 
49
60
  const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
50
61
  questionToEdit,
@@ -59,36 +70,45 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
59
70
  onQuestionEdit,
60
71
  }) => {
61
72
  const { t } = useTranslation();
62
- const { fieldTypes, questionTypes } = useConfig();
63
- const [max, setMax] = useState("");
64
- const [min, setMin] = useState("");
65
- const [questionLabel, setQuestionLabel] = useState("");
66
- const [questionType, setQuestionType] = useState("");
67
- const [isQuestionRequired, setIsQuestionRequired] = useState(false);
68
- const [fieldType, setFieldType] = useState<RenderType>(null);
69
- const [questionId, setQuestionId] = useState("");
70
- const [selectedConcept, setSelectedConcept] = useState(null);
71
- const [conceptMappings, setConceptMappings] = useState<ConceptMapping[]>(
72
- questionToEdit.questionOptions.conceptMappings
73
+ const { fieldTypes, questionTypes }: Config = useConfig();
74
+
75
+ const [answersChanged, setAnswersChanged] = useState(false);
76
+ const [answersFromConcept, setAnswersFromConcept] = useState<
77
+ Array<{
78
+ concept: string;
79
+ label: string;
80
+ }>
81
+ >([]);
82
+ const [conceptMappings, setConceptMappings] = useState<Array<ConceptMapping> | undefined>(
83
+ questionToEdit.questionOptions.conceptMappings,
73
84
  );
74
- const [rows, setRows] = useState(2);
75
- const [conceptToLookup, setConceptToLookup] = useState("");
76
- const [answersFromConcept, setAnswersFromConcept] = useState([]);
77
- const [selectedAnswers, setSelectedAnswers] = useState([]);
85
+ const [conceptToLookup, setConceptToLookup] = useState('');
86
+ const [fieldType, setFieldType] = useState<RenderType | null>(null);
87
+ const [isQuestionRequired, setIsQuestionRequired] = useState(false);
88
+ const [max, setMax] = useState('');
89
+ const [min, setMin] = useState('');
90
+ const [questionId, setQuestionId] = useState('');
91
+ const [questionLabel, setQuestionLabel] = useState('');
92
+ const [questionType, setQuestionType] = useState<QuestionType | null>(null);
93
+ const [rows, setRows] = useState('');
94
+ const [selectedAnswers, setSelectedAnswers] = useState<
95
+ Array<{
96
+ id: string;
97
+ text: string;
98
+ }>
99
+ >([]);
100
+ const [selectedConcept, setSelectedConcept] = useState<Concept | null>(null);
101
+
78
102
  const { concepts, isLoadingConcepts } = useConceptLookup(conceptToLookup);
79
- const { conceptName, isLoadingConceptName } = useConceptName(
80
- questionToEdit.questionOptions.concept
103
+ const { conceptName, conceptNameLookupError, isLoadingConceptName } = useConceptName(
104
+ questionToEdit.questionOptions.concept,
81
105
  );
82
- const [answersChanged, setAnswersChanged] = useState(false);
83
106
 
84
- const hasConceptChanged =
85
- selectedConcept &&
86
- questionToEdit?.questionOptions?.concept !== selectedConcept;
107
+ const hasConceptChanged = selectedConcept && questionToEdit?.questionOptions?.concept !== selectedConcept?.uuid;
87
108
 
88
- const debouncedSearch = useMemo(
89
- () => debounce((searchTerm) => setConceptToLookup(searchTerm), 500),
90
- []
91
- );
109
+ const debouncedSearch = useMemo(() => {
110
+ return debounce((searchTerm: string) => setConceptToLookup(searchTerm), 500) as (searchTerm: string) => void;
111
+ }, []);
92
112
 
93
113
  const handleConceptChange = (searchTerm: string) => {
94
114
  if (searchTerm) {
@@ -97,26 +117,24 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
97
117
  };
98
118
 
99
119
  const handleConceptSelect = (concept: Concept) => {
100
- setConceptToLookup("");
120
+ setConceptToLookup('');
101
121
  setSelectedAnswers([]);
102
122
  setSelectedConcept(concept);
103
123
  setConceptMappings(
104
124
  concept?.mappings?.map((conceptMapping) => {
105
- const data = conceptMapping.display.split(": ");
125
+ const data = conceptMapping.display.split(': ');
106
126
  return {
107
127
  relationship: conceptMapping.conceptMapType.display,
108
128
  type: data[0],
109
129
  value: data[1],
110
130
  };
111
- })
131
+ }),
112
132
  );
113
133
  setAnswersFromConcept(
114
- concept?.answers?.length
115
- ? concept.answers.map((answer) => ({
116
- concept: answer?.uuid,
117
- label: answer?.display,
118
- }))
119
- : []
134
+ concept?.answers?.map((answer) => ({
135
+ concept: answer?.uuid,
136
+ label: answer?.display,
137
+ })) ?? [],
120
138
  );
121
139
  };
122
140
 
@@ -133,7 +151,7 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
133
151
  });
134
152
  });
135
153
 
136
- const questionIds = flattenDeep(nestedIds);
154
+ const questionIds: Array<string> = flattenDeep(nestedIds);
137
155
 
138
156
  return questionIds.includes(idToTest);
139
157
  };
@@ -152,11 +170,7 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
152
170
  }));
153
171
  } else if (hasConceptChanged && answersFromConcept.length === 0) {
154
172
  mappedAnswers = [];
155
- } else if (
156
- hasConceptChanged &&
157
- answersFromConcept?.length > 0 &&
158
- selectedAnswers?.length
159
- ) {
173
+ } else if (hasConceptChanged && answersFromConcept?.length > 0 && selectedAnswers?.length) {
160
174
  mappedAnswers = selectedAnswers?.length
161
175
  ? selectedAnswers.map((answer) => ({
162
176
  concept: answer.id,
@@ -171,30 +185,22 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
171
185
  const data = {
172
186
  label: questionLabel ? questionLabel : questionToEdit.label,
173
187
  type: questionType ? questionType : questionToEdit.type,
174
- required: isQuestionRequired
175
- ? isQuestionRequired
176
- : /true/.test(questionToEdit?.required?.toString()),
188
+ required: isQuestionRequired ? isQuestionRequired : /true/.test(questionToEdit?.required?.toString()),
177
189
  id: questionId ? questionId : questionToEdit.id,
178
190
  questionOptions: {
179
- rendering: fieldType
180
- ? fieldType
181
- : questionToEdit.questionOptions.rendering,
182
- concept: selectedConcept?.uuid
183
- ? selectedConcept.uuid
184
- : questionToEdit.questionOptions.concept,
185
- conceptMappings: conceptMappings?.length
186
- ? conceptMappings
187
- : questionToEdit.questionOptions.conceptMappings,
191
+ rendering: fieldType ? fieldType : questionToEdit.questionOptions.rendering,
192
+ concept: selectedConcept?.uuid ? selectedConcept.uuid : questionToEdit.questionOptions.concept,
193
+ conceptMappings: conceptMappings?.length ? conceptMappings : questionToEdit.questionOptions.conceptMappings,
188
194
  answers: mappedAnswers,
189
195
  },
190
196
  };
191
197
 
192
- schema.pages[pageIndex].sections[sectionIndex].questions[questionIndex] =
193
- data;
198
+ schema.pages[pageIndex].sections[sectionIndex].questions[questionIndex] = data;
199
+
194
200
  onSchemaChange({ ...schema });
195
201
  resetIndices();
196
- setQuestionLabel("");
197
- setQuestionId("");
202
+ setQuestionLabel('');
203
+ setQuestionId('');
198
204
  setIsQuestionRequired(false);
199
205
  setQuestionType(null);
200
206
  setFieldType(null);
@@ -202,41 +208,38 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
202
208
  setConceptMappings([]);
203
209
  setSelectedAnswers([]);
204
210
  onQuestionEdit(null);
211
+
205
212
  showToast({
206
- title: t("success", "Success!"),
207
- kind: "success",
213
+ title: t('success', 'Success!'),
214
+ kind: 'success',
208
215
  critical: true,
209
- description: t("questionUpdated", "Question updated"),
216
+ description: t('questionUpdated', 'Question updated'),
210
217
  });
218
+
211
219
  onModalChange(false);
212
220
  } catch (error) {
213
- showNotification({
214
- title: t("errorUpdatingQuestion", "Error updating question"),
215
- kind: "error",
216
- critical: true,
217
- description: error?.message,
218
- });
221
+ if (error instanceof Error) {
222
+ showNotification({
223
+ title: t('errorUpdatingQuestion', 'Error updating question'),
224
+ kind: 'error',
225
+ critical: true,
226
+ description: error?.message,
227
+ });
228
+ }
219
229
  }
220
230
  };
221
231
 
222
232
  return (
223
- <ComposedModal
224
- open={showModal}
225
- onClose={() => onModalChange(false)}
226
- preventCloseOnClickOutside
227
- >
228
- <ModalHeader title={t("editQuestion", "Edit question")} />
229
- <Form
230
- className={styles.form}
231
- onSubmit={(event) => event.preventDefault()}
232
- >
233
+ <ComposedModal open={showModal} onClose={() => onModalChange(false)} preventCloseOnClickOutside>
234
+ <ModalHeader title={t('editQuestion', 'Edit question')} />
235
+ <Form className={styles.form} onSubmit={(event: React.SyntheticEvent) => event.preventDefault()}>
233
236
  <ModalBody hasScrollingContent>
234
237
  <Stack gap={5}>
235
238
  <TextInput
236
239
  defaultValue={questionToEdit.label}
237
240
  id={questionToEdit.id}
238
- labelText={t("questionLabel", "Label")}
239
- onChange={(event) => setQuestionLabel(event.target.value)}
241
+ labelText={t('questionLabel', 'Label')}
242
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => setQuestionLabel(event.target.value)}
240
243
  required
241
244
  />
242
245
 
@@ -244,45 +247,35 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
244
247
  defaultValue={questionToEdit.id}
245
248
  id="questionId"
246
249
  invalid={questionIdExists(questionId)}
247
- invalidText={t(
248
- "questionIdExists",
249
- "This question ID already exists in your schema"
250
- )}
251
- labelText={t(
252
- "questionId",
253
- "Question ID (prefer using camel-case for IDs)"
254
- )}
255
- onChange={(event) => setQuestionId(event.target.value)}
250
+ invalidText={t('questionIdExists', 'This question ID already exists in your schema')}
251
+ labelText={t('questionId', 'Question ID (prefer using camel-case for IDs)')}
252
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => setQuestionId(event.target.value)}
256
253
  placeholder={t(
257
- "questionIdPlaceholder",
258
- 'Enter a unique ID e.g. "anaesthesiaType" for a question asking about the type of anaesthesia.'
254
+ 'questionIdPlaceholder',
255
+ 'Enter a unique ID e.g. "anaesthesiaType" for a question asking about the type of anaesthesia.',
259
256
  )}
260
257
  required
261
258
  />
262
259
 
263
260
  <RadioButtonGroup
264
- defaultSelected={
265
- /true/.test(questionToEdit?.required?.toString())
266
- ? "required"
267
- : "optional"
268
- }
261
+ defaultSelected={/true/.test(questionToEdit?.required?.toString()) ? 'required' : 'optional'}
269
262
  name="isQuestionRequired"
270
263
  legendText={t(
271
- "isQuestionRequiredOrOptional",
272
- "Is this question a required or optional field? Required fields must be answered before the form can be submitted."
264
+ 'isQuestionRequiredOrOptional',
265
+ 'Is this question a required or optional field? Required fields must be answered before the form can be submitted.',
273
266
  )}
274
267
  >
275
268
  <RadioButton
276
269
  id="questionIsNotRequired"
277
270
  defaultChecked={true}
278
- labelText={t("optional", "Optional")}
271
+ labelText={t('optional', 'Optional')}
279
272
  onClick={() => setIsQuestionRequired(false)}
280
273
  value="optional"
281
274
  />
282
275
  <RadioButton
283
276
  id="questionIsRequired"
284
277
  defaultChecked={false}
285
- labelText={t("required", "Required")}
278
+ labelText={t('required', 'Required')}
286
279
  onClick={() => setIsQuestionRequired(true)}
287
280
  value="required"
288
281
  />
@@ -290,99 +283,85 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
290
283
 
291
284
  <Select
292
285
  defaultValue={questionToEdit.type}
293
- onChange={(event) => setQuestionType(event.target.value)}
294
- id={"questionType"}
295
- invalidText={t("typeRequired", "Type is required")}
296
- labelText={t("questionType", "Question type")}
286
+ onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
287
+ setQuestionType(event.target.value as QuestionType)
288
+ }
289
+ id={'questionType'}
290
+ invalidText={t('typeRequired', 'Type is required')}
291
+ labelText={t('questionType', 'Question type')}
297
292
  required
298
293
  >
299
- {!questionType && (
300
- <SelectItem
301
- text={t("chooseQuestionType", "Choose a question type")}
302
- value=""
303
- />
304
- )}
294
+ {!questionType && <SelectItem text={t('chooseQuestionType', 'Choose a question type')} value="" />}
305
295
  {questionTypes.map((questionType, key) => (
306
- <SelectItem
307
- text={questionType}
308
- value={questionType}
309
- key={key}
310
- />
296
+ <SelectItem text={questionType} value={questionType} key={key} />
311
297
  ))}
312
298
  </Select>
313
299
 
314
300
  <Select
315
301
  defaultValue={questionToEdit.questionOptions.rendering}
316
- onChange={(event) => setFieldType(event.target.value)}
302
+ onChange={(event: React.ChangeEvent<HTMLSelectElement>) => setFieldType(event.target.value as RenderType)}
317
303
  id="renderingType"
318
- invalidText={t(
319
- "validFieldTypeRequired",
320
- "A valid field type value is required"
321
- )}
322
- labelText={t("fieldType", "Field type")}
304
+ invalidText={t('validFieldTypeRequired', 'A valid field type value is required')}
305
+ labelText={t('fieldType', 'Field type')}
323
306
  required
324
307
  >
325
- {!fieldType && (
326
- <SelectItem
327
- text={t("chooseFieldType", "Choose a field type")}
328
- value=""
329
- />
330
- )}
308
+ {!fieldType && <SelectItem text={t('chooseFieldType', 'Choose a field type')} value="" />}
331
309
  {fieldTypes.map((fieldType, key) => (
332
310
  <SelectItem text={fieldType} value={fieldType} key={key} />
333
311
  ))}
334
312
  </Select>
335
313
 
336
- {fieldType === "number" ? (
314
+ {fieldType === 'number' ? (
337
315
  <>
338
316
  <TextInput
339
317
  id="min"
340
318
  labelText="Min"
341
- value={min || ""}
342
- onChange={(event) => setMin(event.target.value)}
319
+ value={min || ''}
320
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => setMin(event.target.value)}
343
321
  required
344
322
  />
345
323
  <TextInput
346
324
  id="max"
347
325
  labelText="Max"
348
- value={max || ""}
349
- onChange={(event) => setMax(event.target.value)}
326
+ value={max || ''}
327
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => setMax(event.target.value)}
350
328
  required
351
329
  />
352
330
  </>
353
- ) : fieldType === "textarea" ? (
331
+ ) : fieldType === 'textarea' ? (
354
332
  <TextInput
355
333
  id="textAreaRows"
356
- labelText={t("rows", "Rows")}
357
- value={rows || ""}
358
- onChange={(event) => setRows(event.target.value)}
334
+ labelText={t('rows', 'Rows')}
335
+ value={rows || ''}
336
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => setRows(event.target.value)}
359
337
  required
360
338
  />
361
339
  ) : null}
362
340
 
363
- {fieldType !== "ui-select-extended" && (
341
+ {fieldType !== 'ui-select-extended' && (
364
342
  <div>
365
343
  <FormLabel className={styles.label}>
366
- {t("searchForBackingConcept", "Search for a backing concept")}
344
+ {t('searchForBackingConcept', 'Search for a backing concept')}
367
345
  </FormLabel>
368
- {isLoadingConceptName ? (
369
- <InlineLoading
370
- className={styles.loader}
371
- description={t("loading", "Loading") + "..."}
346
+ {conceptNameLookupError ? (
347
+ <InlineNotification
348
+ kind="error"
349
+ lowContrast
350
+ className={styles.error}
351
+ title={t('errorFetchingConceptName', "Couldn't resolve concept name")}
352
+ subtitle={t('conceptDoesNotExist', 'The linked concept does not exist in your dictionary.')}
372
353
  />
354
+ ) : null}
355
+ {isLoadingConceptName ? (
356
+ <InlineLoading className={styles.loader} description={t('loading', 'Loading') + '...'} />
373
357
  ) : (
374
358
  <>
375
359
  <Search
376
360
  defaultValue={conceptName}
377
361
  id="conceptLookup"
378
362
  onClear={() => setSelectedConcept(null)}
379
- onChange={(e) =>
380
- handleConceptChange(e.target.value?.trim())
381
- }
382
- placeholder={t(
383
- "searchConcept",
384
- "Search using a concept name or UUID"
385
- )}
363
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleConceptChange(e.target.value?.trim())}
364
+ placeholder={t('searchConcept', 'Search using a concept name or UUID')}
386
365
  required
387
366
  size="md"
388
367
  value={selectedConcept?.display}
@@ -391,12 +370,9 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
391
370
  if (!conceptToLookup) return null;
392
371
  if (isLoadingConcepts)
393
372
  return (
394
- <InlineLoading
395
- className={styles.loader}
396
- description={t("searching", "Searching") + "..."}
397
- />
373
+ <InlineLoading className={styles.loader} description={t('searching', 'Searching') + '...'} />
398
374
  );
399
- if (concepts && concepts?.length && !isLoadingConcepts) {
375
+ if (concepts?.length && !isLoadingConcepts) {
400
376
  return (
401
377
  <ul className={styles.conceptList}>
402
378
  {concepts?.map((concept, index) => (
@@ -416,10 +392,7 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
416
392
  <Layer>
417
393
  <Tile className={styles.emptyResults}>
418
394
  <span>
419
- {t(
420
- "noMatchingConcepts",
421
- "No concepts were found that match"
422
- )}{" "}
395
+ {t('noMatchingConcepts', 'No concepts were found that match')}{' '}
423
396
  <strong>"{conceptToLookup}".</strong>
424
397
  </span>
425
398
  </Tile>
@@ -427,19 +400,16 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
427
400
  <div className={styles.oclLauncherBanner}>
428
401
  {
429
402
  <p className={styles.bodyShort01}>
430
- {t(
431
- "conceptSearchHelpText",
432
- "Can't find a concept?"
433
- )}
403
+ {t('conceptSearchHelpText', "Can't find a concept?")}
434
404
  </p>
435
405
  }
436
406
  <a
437
407
  className={styles.oclLink}
438
408
  target="_blank"
439
409
  rel="noopener noreferrer"
440
- href={"https://app.openconceptlab.org/"}
410
+ href={'https://app.openconceptlab.org/'}
441
411
  >
442
- {t("searchInOCL", "Search in OCL")}
412
+ {t('searchInOCL', 'Search in OCL')}
443
413
  <ArrowUpRight size={16} />
444
414
  </a>
445
415
  </div>
@@ -451,25 +421,23 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
451
421
  </div>
452
422
  )}
453
423
 
454
- {conceptMappings && conceptMappings.length ? (
424
+ {conceptMappings?.length ? (
455
425
  <FormGroup>
456
- <FormLabel className={styles.label}>
457
- {t("mappings", "Mappings")}
458
- </FormLabel>
426
+ <FormLabel className={styles.label}>{t('mappings', 'Mappings')}</FormLabel>
459
427
  <table className={styles.tableStriped}>
460
428
  <thead>
461
429
  <tr>
462
- <th>{t("relationship", "Relationship")}</th>
463
- <th>{t("source", "Source")}</th>
464
- <th>{t("code", "Code")}</th>
430
+ <th>{t('relationship', 'Relationship')}</th>
431
+ <th>{t('source', 'Source')}</th>
432
+ <th>{t('code', 'Code')}</th>
465
433
  </tr>
466
434
  </thead>
467
435
  <tbody>
468
436
  {conceptMappings.map((mapping, index) => (
469
437
  <tr key={`mapping-${index}`}>
470
- <td>{mapping.relationship ?? "--"}</td>
471
- <td>{mapping.type ?? "--"}</td>
472
- <td>{mapping.value ?? "--"}</td>
438
+ <td>{mapping.relationship ?? '--'}</td>
439
+ <td>{mapping.type ?? '--'}</td>
440
+ <td>{mapping.value ?? '--'}</td>
473
441
  </tr>
474
442
  ))}
475
443
  </tbody>
@@ -477,48 +445,40 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
477
445
  </FormGroup>
478
446
  ) : null}
479
447
 
480
- {!hasConceptChanged &&
481
- questionToEdit?.questionOptions?.answers &&
482
- questionToEdit?.questionOptions.answers?.length ? (
448
+ {!hasConceptChanged && questionToEdit?.questionOptions.answers?.length ? (
483
449
  <MultiSelect
484
450
  className={styles.multiSelect}
485
451
  direction="top"
486
452
  id="selectAnswers"
487
- itemToString={(item) => item.text}
488
- initialSelectedItems={questionToEdit?.questionOptions?.answers?.map(
489
- (answer) => ({
490
- id: answer.concept,
491
- text: answer.label,
492
- })
493
- )}
494
- items={questionToEdit?.questionOptions?.answers?.map(
495
- (answer) => ({
496
- id: answer.concept,
497
- text: answer.label ?? "",
498
- })
499
- )}
500
- onChange={({ selectedItems }) => {
453
+ itemToString={(item: Item) => item.text}
454
+ initialSelectedItems={questionToEdit?.questionOptions?.answers?.map((answer) => ({
455
+ id: answer.concept,
456
+ text: answer.label,
457
+ }))}
458
+ items={questionToEdit?.questionOptions?.answers?.map((answer) => ({
459
+ id: answer.concept,
460
+ text: answer.label ?? '',
461
+ }))}
462
+ onChange={({
463
+ selectedItems,
464
+ }: {
465
+ selectedItems: Array<{
466
+ id: string;
467
+ text: string;
468
+ }>;
469
+ }) => {
501
470
  setAnswersChanged(true);
502
471
  setSelectedAnswers(selectedItems.sort());
503
472
  }}
504
473
  size="md"
505
- titleText={t(
506
- "selectAnswersToDisplay",
507
- "Select answers to display"
508
- )}
474
+ titleText={t('selectAnswersToDisplay', 'Select answers to display')}
509
475
  />
510
476
  ) : null}
511
477
 
512
- {!hasConceptChanged &&
513
- questionToEdit?.questionOptions?.answers?.length &&
514
- !answersChanged ? (
478
+ {!hasConceptChanged && questionToEdit?.questionOptions?.answers?.length && !answersChanged ? (
515
479
  <div>
516
480
  {questionToEdit?.questionOptions?.answers?.map((answer) => (
517
- <Tag
518
- className={styles.tag}
519
- key={answer?.concept}
520
- type={"blue"}
521
- >
481
+ <Tag className={styles.tag} key={answer?.concept} type={'blue'}>
522
482
  {answer?.label}
523
483
  </Tag>
524
484
  ))}
@@ -530,30 +490,28 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
530
490
  className={styles.multiSelect}
531
491
  direction="top"
532
492
  id="selectAnswers"
533
- itemToString={(item) => item.text}
493
+ itemToString={(item: Item) => item.text}
534
494
  items={answersFromConcept.map((answer) => ({
535
495
  id: answer.concept,
536
496
  text: answer.label,
537
497
  }))}
538
- onChange={({ selectedItems }) =>
539
- setSelectedAnswers(selectedItems.sort())
540
- }
498
+ onChange={({
499
+ selectedItems,
500
+ }: {
501
+ selectedItems: Array<{
502
+ id: string;
503
+ text: string;
504
+ }>;
505
+ }) => setSelectedAnswers(selectedItems.sort())}
541
506
  size="md"
542
- titleText={t(
543
- "selectAnswersToDisplay",
544
- "Select answers to display"
545
- )}
507
+ titleText={t('selectAnswersToDisplay', 'Select answers to display')}
546
508
  />
547
509
  ) : null}
548
510
 
549
- {(hasConceptChanged || answersChanged) && (
511
+ {(hasConceptChanged ?? answersChanged) && (
550
512
  <div>
551
513
  {selectedAnswers.map((selectedAnswer) => (
552
- <Tag
553
- className={styles.tag}
554
- key={selectedAnswer.id}
555
- type={"blue"}
556
- >
514
+ <Tag className={styles.tag} key={selectedAnswer.id} type={'blue'}>
557
515
  {selectedAnswer.text}
558
516
  </Tag>
559
517
  ))}
@@ -563,10 +521,10 @@ const EditQuestionModal: React.FC<EditQuestionModalProps> = ({
563
521
  </ModalBody>
564
522
  <ModalFooter>
565
523
  <Button onClick={() => onModalChange(false)} kind="secondary">
566
- {t("cancel", "Cancel")}
524
+ {t('cancel', 'Cancel')}
567
525
  </Button>
568
526
  <Button onClick={handleUpdateQuestion}>
569
- <span>{t("save", "Save")}</span>
527
+ <span>{t('save', 'Save')}</span>
570
528
  </Button>
571
529
  </ModalFooter>
572
530
  </Form>