@openmrs/esm-form-builder-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.
Files changed (52) hide show
  1. package/LICENSE +401 -0
  2. package/README.md +35 -0
  3. package/package.json +106 -0
  4. package/src/components/action-buttons/action-buttons.component.tsx +185 -0
  5. package/src/components/action-buttons/action-buttons.scss +16 -0
  6. package/src/components/dashboard/dashboard.component.tsx +309 -0
  7. package/src/components/dashboard/dashboard.scss +112 -0
  8. package/src/components/dashboard/dashboard.test.tsx +208 -0
  9. package/src/components/empty-state/empty-data-illustration.component.tsx +51 -0
  10. package/src/components/empty-state/empty-state.component.tsx +41 -0
  11. package/src/components/empty-state/empty-state.scss +55 -0
  12. package/src/components/error-state/error-state.component.tsx +37 -0
  13. package/src/components/error-state/error-state.scss +49 -0
  14. package/src/components/form-editor/form-editor.component.tsx +125 -0
  15. package/src/components/form-editor/form-editor.scss +33 -0
  16. package/src/components/form-renderer/form-renderer.component.tsx +123 -0
  17. package/src/components/form-renderer/form-renderer.scss +57 -0
  18. package/src/components/interactive-builder/add-question-modal.component.tsx +427 -0
  19. package/src/components/interactive-builder/delete-page-modal.component.tsx +89 -0
  20. package/src/components/interactive-builder/delete-question-modal.component.tsx +93 -0
  21. package/src/components/interactive-builder/delete-section-modal.component.tsx +91 -0
  22. package/src/components/interactive-builder/edit-question-modal.component.tsx +465 -0
  23. package/src/components/interactive-builder/editable-value.component.tsx +64 -0
  24. package/src/components/interactive-builder/editable-value.scss +23 -0
  25. package/src/components/interactive-builder/interactive-builder.component.tsx +569 -0
  26. package/src/components/interactive-builder/interactive-builder.scss +100 -0
  27. package/src/components/interactive-builder/new-form-modal.component.tsx +86 -0
  28. package/src/components/interactive-builder/page-modal.component.tsx +91 -0
  29. package/src/components/interactive-builder/question-modal.scss +35 -0
  30. package/src/components/interactive-builder/section-modal.component.tsx +94 -0
  31. package/src/components/interactive-builder/value-editor.component.tsx +55 -0
  32. package/src/components/interactive-builder/value-editor.scss +10 -0
  33. package/src/components/modals/save-form.component.tsx +310 -0
  34. package/src/components/modals/save-form.scss +5 -0
  35. package/src/components/schema-editor/schema-editor.component.tsx +191 -0
  36. package/src/components/schema-editor/schema-editor.scss +26 -0
  37. package/src/config-schema.ts +47 -0
  38. package/src/constants.ts +3 -0
  39. package/src/declarations.d.tsx +2 -0
  40. package/src/form-builder-app-menu-link.component.tsx +13 -0
  41. package/src/forms.resource.ts +178 -0
  42. package/src/hooks/useClobdata.ts +20 -0
  43. package/src/hooks/useConceptLookup.ts +18 -0
  44. package/src/hooks/useConceptName.ts +18 -0
  45. package/src/hooks/useEncounterTypes.ts +18 -0
  46. package/src/hooks/useForm.ts +18 -0
  47. package/src/hooks/useForms.ts +20 -0
  48. package/src/index.ts +70 -0
  49. package/src/root.component.tsx +19 -0
  50. package/src/setup-tests.ts +11 -0
  51. package/src/test-helpers.tsx +37 -0
  52. package/src/types.ts +132 -0
@@ -0,0 +1,123 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { ErrorBoundary } from "react-error-boundary";
3
+ import { useTranslation } from "react-i18next";
4
+ import { Button, InlineLoading, Tile } from "@carbon/react";
5
+ import { OHRIFormSchema, OHRIForm } from "@ohri/openmrs-ohri-form-engine-lib";
6
+ import { useConfig } from "@openmrs/esm-framework";
7
+
8
+ import ActionButtons from "../action-buttons/action-buttons.component";
9
+ import styles from "./form-renderer.scss";
10
+
11
+ type FormRendererProps = {
12
+ isLoading: boolean;
13
+ onSchemaChange?: (schema: OHRIFormSchema) => void;
14
+ schema: OHRIFormSchema;
15
+ };
16
+
17
+ const FormRenderer: React.FC<FormRendererProps> = ({ isLoading, schema }) => {
18
+ const { t } = useTranslation();
19
+ const { patientUuid } = useConfig();
20
+
21
+ const dummySchema: OHRIFormSchema = {
22
+ encounterType: "",
23
+ name: "Test Form",
24
+ pages: [
25
+ {
26
+ label: "Test Page",
27
+ sections: [
28
+ {
29
+ label: "Test Section",
30
+ isExpanded: "true",
31
+ questions: [
32
+ {
33
+ label: "Test Question",
34
+ type: "obs",
35
+ questionOptions: {
36
+ rendering: "text",
37
+ concept: "xxxx",
38
+ },
39
+ id: "testQuestion",
40
+ },
41
+ ],
42
+ },
43
+ ],
44
+ },
45
+ ],
46
+ processor: "EncounterFormProcessor",
47
+ referencedForms: [],
48
+ uuid: "xxx",
49
+ };
50
+
51
+ const [schemaToRender, setSchemaToRender] =
52
+ useState<OHRIFormSchema>(dummySchema);
53
+
54
+ useEffect(() => {
55
+ if (schema) {
56
+ setSchemaToRender(schema);
57
+ }
58
+ }, [schema]);
59
+
60
+ if (isLoading) {
61
+ return (
62
+ <div className={styles.loadingContainer}>
63
+ <InlineLoading
64
+ className={styles.loader}
65
+ description={t("loading", "Loading") + "..."}
66
+ />
67
+ </div>
68
+ );
69
+ }
70
+
71
+ return (
72
+ <>
73
+ <ActionButtons schema={schema} t={t} />
74
+
75
+ <div className={styles.container}>
76
+ {!schema && (
77
+ <Tile className={styles.emptyStateTile}>
78
+ <h4 className={styles.heading}>
79
+ {t("noSchemaLoaded", "No schema loaded")}
80
+ </h4>
81
+ <p className={styles.helperText}>
82
+ {t(
83
+ "formRendererHelperText",
84
+ "Load a form schema in the Schema Editor to the left to see it rendered here by the Form Engine."
85
+ )}
86
+ </p>
87
+ </Tile>
88
+ )}
89
+ {schema === schemaToRender && (
90
+ <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
91
+ <OHRIForm
92
+ formJson={schemaToRender}
93
+ mode={"enter"}
94
+ patientUUID={patientUuid}
95
+ />
96
+ </ErrorBoundary>
97
+ )}
98
+ </div>
99
+ </>
100
+ );
101
+ };
102
+
103
+ function ErrorFallback({ error, resetErrorBoundary }) {
104
+ const { t } = useTranslation();
105
+ return (
106
+ <Tile className={styles.errorStateTile}>
107
+ <h4 className={styles.heading}>
108
+ {t(
109
+ "problemLoadingPreview",
110
+ "There was a problem loading the schema preview"
111
+ )}
112
+ </h4>
113
+ <p className={styles.helperText}>
114
+ <pre>{error.message}</pre>
115
+ </p>
116
+ <Button kind="primary" onClick={resetErrorBoundary}>
117
+ {t("tryAgain", "Try again")}
118
+ </Button>
119
+ </Tile>
120
+ );
121
+ }
122
+
123
+ export default FormRenderer;
@@ -0,0 +1,57 @@
1
+ @use '@carbon/styles/scss/colors';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @use '@carbon/styles/scss/type';
4
+ @import '~@openmrs/esm-styleguide/src/vars';
5
+
6
+ .container {
7
+ background-color: white;
8
+ padding: 1rem;
9
+ overflow-y: scroll;
10
+ height: 100vh;
11
+ }
12
+
13
+ .loadingContainer {
14
+ @extend .container;
15
+ background-color: $ui-01;
16
+ margin-top: 5rem;
17
+ }
18
+
19
+ .loader {
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ height: inherit;
24
+ }
25
+
26
+ .emptyStateTile {
27
+ width: 80%;
28
+ margin: 1.25rem auto;
29
+ display: flex;
30
+ flex-flow: column wrap;
31
+ align-items: center;
32
+ justify-content: center;
33
+ padding: 1.5rem;
34
+ }
35
+
36
+ .errorStateTile {
37
+ @extend .emptyStateTile;
38
+
39
+ .helperText {
40
+ margin: 0.75rem 0;
41
+ }
42
+
43
+ button {
44
+ margin-top: 0.25rem;
45
+ }
46
+ }
47
+
48
+ .heading {
49
+ @include type.type-style('heading-compact-02');
50
+ color: colors.$gray-100;
51
+ }
52
+
53
+ .helperText {
54
+ margin-top: 0.5rem;
55
+ @include type.type-style('body-01');
56
+ color: colors.$gray-90;
57
+ }
@@ -0,0 +1,427 @@
1
+ import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ Form,
7
+ FormGroup,
8
+ FormLabel,
9
+ Layer,
10
+ InlineLoading,
11
+ ModalBody,
12
+ ModalFooter,
13
+ ModalHeader,
14
+ MultiSelect,
15
+ RadioButton,
16
+ RadioButtonGroup,
17
+ Search,
18
+ Select,
19
+ SelectItem,
20
+ Stack,
21
+ Tag,
22
+ TextInput,
23
+ Tile,
24
+ } from "@carbon/react";
25
+ import flattenDeep from "lodash-es/flattenDeep";
26
+ import { showNotification, showToast, useConfig } from "@openmrs/esm-framework";
27
+ import {
28
+ Answer,
29
+ Concept,
30
+ ConceptMapping,
31
+ FieldTypes,
32
+ Question,
33
+ Schema,
34
+ } from "../../types";
35
+ import { useConceptLookup } from "../../hooks/useConceptLookup";
36
+ import styles from "./question-modal.scss";
37
+
38
+ type AddQuestionModalProps = {
39
+ onModalChange: (showModal: boolean) => void;
40
+ onQuestionEdit: (question: Question) => void;
41
+ onSchemaChange: (schema: Schema) => void;
42
+ pageIndex: number;
43
+ questionIndex: number;
44
+ questionToEdit: Question;
45
+ resetIndices: () => void;
46
+ schema: Schema;
47
+ sectionIndex: number;
48
+ showModal: boolean;
49
+ };
50
+
51
+ const AddQuestionModal: React.FC<AddQuestionModalProps> = ({
52
+ schema,
53
+ onSchemaChange,
54
+ pageIndex,
55
+ sectionIndex,
56
+ questionIndex,
57
+ resetIndices,
58
+ showModal,
59
+ onModalChange,
60
+ questionToEdit,
61
+ onQuestionEdit,
62
+ }) => {
63
+ const { t } = useTranslation();
64
+ const { fieldTypes, questionTypes } = useConfig();
65
+ const [max, setMax] = useState("");
66
+ const [min, setMin] = useState("");
67
+ const [questionLabel, setQuestionLabel] = useState("");
68
+ const [questionType, setQuestionType] = useState("");
69
+ const [isQuestionRequired, setIsQuestionRequired] = useState(false);
70
+ const [fieldType, setFieldType] = useState<FieldTypes>(null);
71
+ const [questionId, setQuestionId] = useState("");
72
+ const [answers, setAnswers] = useState<Answer[]>([]);
73
+ const [selectedConcept, setSelectedConcept] = useState(null);
74
+ const [conceptMappings, setConceptMappings] = useState<ConceptMapping[]>([]);
75
+ const [rows, setRows] = useState(2);
76
+ const [conceptToLookup, setConceptToLookup] = useState("");
77
+ const [selectedAnswers, setSelectedAnswers] = useState([]);
78
+ const { concepts, isLoadingConcepts } = useConceptLookup(conceptToLookup);
79
+
80
+ const handleConceptChange = (event) => {
81
+ setConceptToLookup(event.target.value);
82
+ };
83
+
84
+ const handleConceptSelect = (concept: Concept) => {
85
+ setConceptToLookup("");
86
+ setSelectedConcept(concept);
87
+ setAnswers(
88
+ concept?.answers?.map((answer) => ({
89
+ concept: answer?.uuid,
90
+ label: answer?.display,
91
+ }))
92
+ );
93
+ setConceptMappings(
94
+ concept?.mappings?.map((conceptMapping) => {
95
+ let data = conceptMapping.display.split(": ");
96
+ return { type: data[0], value: data[1] };
97
+ })
98
+ );
99
+ };
100
+
101
+ const questionIdExists = (idToTest) => {
102
+ if (questionToEdit?.id === idToTest) {
103
+ return false;
104
+ }
105
+
106
+ const nestedIds = schema?.pages?.map((page) => {
107
+ return page?.sections?.map((section) => {
108
+ return section?.questions?.map((question) => {
109
+ return question.id;
110
+ });
111
+ });
112
+ });
113
+
114
+ const questionIds = flattenDeep(nestedIds);
115
+
116
+ return questionIds.includes(idToTest);
117
+ };
118
+
119
+ const handleCreateQuestion = () => {
120
+ createQuestion();
121
+ onModalChange(false);
122
+ };
123
+
124
+ const createQuestion = () => {
125
+ try {
126
+ const computedQuestionId = `question-${questionIndex + 1}-section-${
127
+ sectionIndex + 1
128
+ }-page-${pageIndex + 1}`;
129
+
130
+ schema.pages[pageIndex]?.sections?.[sectionIndex]?.questions?.push({
131
+ label: questionLabel,
132
+ type: questionType,
133
+ required: isQuestionRequired,
134
+ id: questionId ?? computedQuestionId,
135
+ questionOptions: {
136
+ rendering: fieldType,
137
+ concept: selectedConcept.uuid,
138
+ conceptMappings: conceptMappings,
139
+ answers: selectedAnswers.map((answer) => ({
140
+ concept: answer.id,
141
+ label: answer.text,
142
+ })),
143
+ },
144
+ validators: [],
145
+ });
146
+
147
+ onSchemaChange({ ...schema });
148
+
149
+ resetIndices();
150
+ setQuestionLabel("");
151
+ setQuestionId("");
152
+ setIsQuestionRequired(false);
153
+ setQuestionType(null);
154
+ setFieldType(null);
155
+ setSelectedConcept(null);
156
+ setConceptMappings([]);
157
+ setAnswers([]);
158
+ setSelectedAnswers([]);
159
+
160
+ showToast({
161
+ title: t("success", "Success!"),
162
+ kind: "success",
163
+ critical: true,
164
+ description: t("questionCreated", "New question created"),
165
+ });
166
+ } catch (error) {
167
+ showNotification({
168
+ title: t("errorCreatingQuestion", "Error creating question"),
169
+ kind: "error",
170
+ critical: true,
171
+ description: error?.message,
172
+ });
173
+ }
174
+ };
175
+
176
+ return (
177
+ <ComposedModal open={showModal} onClose={() => onModalChange(false)}>
178
+ <ModalHeader title={t("createNewQuestion", "Create a new question")} />
179
+ <Form onSubmit={(event) => event.preventDefault()}>
180
+ <ModalBody hasScrollingContent>
181
+ <FormGroup legendText={""}>
182
+ <Stack gap={5}>
183
+ <TextInput
184
+ id="questionLabel"
185
+ labelText={t("questionLabel", "Label")}
186
+ placeholder={t("labelPlaceholder", "e.g. Type of Anaesthesia")}
187
+ value={questionLabel}
188
+ onChange={(event) => setQuestionLabel(event.target.value)}
189
+ required
190
+ />
191
+ <RadioButtonGroup
192
+ defaultSelected="optional"
193
+ name="isQuestionRequired"
194
+ legendText={t(
195
+ "isQuestionRequiredOrOptional",
196
+ "Is this question a required or optional field? Required fields must be answered before the form can be submitted."
197
+ )}
198
+ >
199
+ <RadioButton
200
+ id="questionIsNotRequired"
201
+ defaultChecked={true}
202
+ labelText={t("optional", "Optional")}
203
+ onClick={() => setIsQuestionRequired(false)}
204
+ value="optional"
205
+ />
206
+ <RadioButton
207
+ id="questionIsRequired"
208
+ defaultChecked={false}
209
+ labelText={t("required", "Required")}
210
+ onClick={() => setIsQuestionRequired(true)}
211
+ value="required"
212
+ />
213
+ </RadioButtonGroup>
214
+ <Select
215
+ value={questionType}
216
+ onChange={(event) => setQuestionType(event.target.value)}
217
+ id="questionType"
218
+ invalidText={t("typeRequired", "Type is required")}
219
+ labelText={t("questionType", "Question type")}
220
+ required
221
+ >
222
+ {!questionType && (
223
+ <SelectItem
224
+ text={t("chooseQuestionType", "Choose a question type")}
225
+ value=""
226
+ />
227
+ )}
228
+ {questionTypes.map((questionType, key) => (
229
+ <SelectItem
230
+ text={questionType}
231
+ value={questionType}
232
+ key={key}
233
+ />
234
+ ))}
235
+ </Select>
236
+ <Select
237
+ value={fieldType}
238
+ onChange={(event) => setFieldType(event.target.value)}
239
+ id="renderingType"
240
+ invalidText={t(
241
+ "validFieldTypeRequired",
242
+ "A valid field type value is required"
243
+ )}
244
+ labelText={t("fieldType", "Field type")}
245
+ required
246
+ >
247
+ {!fieldType && (
248
+ <SelectItem
249
+ text={t("chooseFieldType", "Choose a field type")}
250
+ value=""
251
+ />
252
+ )}
253
+ {fieldTypes.map((fieldType, key) => (
254
+ <SelectItem text={fieldType} value={fieldType} key={key} />
255
+ ))}
256
+ </Select>
257
+ {fieldType === FieldTypes.Number ? (
258
+ <>
259
+ <TextInput
260
+ id="min"
261
+ labelText="Min"
262
+ value={min || ""}
263
+ onChange={(event) => setMin(event.target.value)}
264
+ required
265
+ />
266
+ <TextInput
267
+ id="max"
268
+ labelText="Max"
269
+ value={max || ""}
270
+ onChange={(event) => setMax(event.target.value)}
271
+ required
272
+ />
273
+ </>
274
+ ) : fieldType === FieldTypes.TextArea ? (
275
+ <TextInput
276
+ id="textAreaRows"
277
+ labelText={t("rows", "Rows")}
278
+ value={rows || ""}
279
+ onChange={(event) => setRows(event.target.value)}
280
+ required
281
+ />
282
+ ) : null}
283
+ <TextInput
284
+ id="questionId"
285
+ invalid={questionIdExists(questionId)}
286
+ invalidText={t(
287
+ "questionIdExists",
288
+ "This question ID already exists in your schema"
289
+ )}
290
+ labelText={t(
291
+ "questionId",
292
+ "Question ID (prefer camel-case for IDs)"
293
+ )}
294
+ value={questionId}
295
+ onChange={(event) => {
296
+ setQuestionId(event.target.value);
297
+ }}
298
+ placeholder={t(
299
+ "questionIdPlaceholder",
300
+ 'Enter a unique ID e.g. "anaesthesiaType" for a question asking about the type of anaesthesia.'
301
+ )}
302
+ required
303
+ />
304
+ {fieldType !== FieldTypes.UiSelectExtended && (
305
+ <div>
306
+ <FormLabel className={styles.label}>
307
+ {t("selectBackingConcept", "Select backing concept")}
308
+ </FormLabel>
309
+ <Search
310
+ size="md"
311
+ id="conceptLookup"
312
+ labelText={t("enterConceptName", "Enter concept name")}
313
+ placeholder={t("searchConcept", "Search concept")}
314
+ onClear={() => setSelectedConcept(null)}
315
+ onChange={handleConceptChange}
316
+ value={(() => {
317
+ if (conceptToLookup) {
318
+ return conceptToLookup;
319
+ }
320
+ if (selectedConcept) {
321
+ return selectedConcept.display;
322
+ }
323
+ return "";
324
+ })()}
325
+ required
326
+ />
327
+ {(() => {
328
+ if (!conceptToLookup) return null;
329
+ if (isLoadingConcepts)
330
+ return (
331
+ <InlineLoading
332
+ className={styles.loader}
333
+ description={t("searching", "Searching") + "..."}
334
+ />
335
+ );
336
+ if (concepts && concepts?.length && !isLoadingConcepts) {
337
+ return (
338
+ <ul className={styles.conceptList}>
339
+ {concepts?.map((concept, index) => (
340
+ <li
341
+ role="menuitem"
342
+ className={styles.concept}
343
+ key={index}
344
+ onClick={() => handleConceptSelect(concept)}
345
+ >
346
+ {concept.display}
347
+ </li>
348
+ ))}
349
+ </ul>
350
+ );
351
+ }
352
+ return (
353
+ <Layer>
354
+ <Tile className={styles.emptyResults}>
355
+ <span>
356
+ {t(
357
+ "noMatchingConcepts",
358
+ "No concepts were found that match"
359
+ )}{" "}
360
+ <strong>"{conceptToLookup}".</strong>
361
+ </span>
362
+ </Tile>
363
+ </Layer>
364
+ );
365
+ })()}
366
+ </div>
367
+ )}
368
+
369
+ {/* Handle Concept Mappings */}
370
+ {/* {conceptMappings && conceptMappings.length ? (
371
+ <div>{JSON.stringify(conceptMappings)}</div>
372
+ ) : null} */}
373
+
374
+ {selectedAnswers.length ? (
375
+ <div>
376
+ {selectedAnswers.map((answer) => (
377
+ <Tag className={styles.tag} key={answer.id} type={"blue"}>
378
+ {answer.text}
379
+ </Tag>
380
+ ))}
381
+ </div>
382
+ ) : null}
383
+
384
+ {answers && answers.length ? (
385
+ <MultiSelect
386
+ id="selectAnswers"
387
+ itemToString={(item) => item.text}
388
+ items={answers.map((answer) => ({
389
+ id: answer.concept,
390
+ text: answer.label,
391
+ }))}
392
+ onChange={({ selectedItems }) =>
393
+ setSelectedAnswers(selectedItems.sort())
394
+ }
395
+ size="md"
396
+ titleText={t(
397
+ "selectAnswersToDisplay",
398
+ "Select answers to display"
399
+ )}
400
+ />
401
+ ) : null}
402
+ </Stack>
403
+ </FormGroup>
404
+ </ModalBody>
405
+ </Form>
406
+ <ModalFooter>
407
+ <Button onClick={() => onModalChange(false)} kind="secondary">
408
+ {t("cancel", "Cancel")}
409
+ </Button>
410
+ <Button
411
+ disabled={
412
+ !questionLabel ||
413
+ !questionId ||
414
+ questionIdExists(questionId) ||
415
+ !fieldType ||
416
+ (fieldType !== FieldTypes.UiSelectExtended && !selectedConcept)
417
+ }
418
+ onClick={handleCreateQuestion}
419
+ >
420
+ <span>{t("save", "Save")}</span>
421
+ </Button>
422
+ </ModalFooter>
423
+ </ComposedModal>
424
+ );
425
+ };
426
+
427
+ export default AddQuestionModal;
@@ -0,0 +1,89 @@
1
+ import React from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ ModalBody,
7
+ ModalFooter,
8
+ ModalHeader,
9
+ } from "@carbon/react";
10
+ import { showNotification, showToast } from "@openmrs/esm-framework";
11
+ import { Schema } from "../../types";
12
+
13
+ type DeletePageModalProps = {
14
+ onModalChange: (showModal: boolean) => void;
15
+ onSchemaChange: (schema: Schema) => void;
16
+ resetIndices: () => void;
17
+ pageIndex: number;
18
+ schema: Schema;
19
+ showModal: boolean;
20
+ };
21
+
22
+ const DeletePageModal: React.FC<DeletePageModalProps> = ({
23
+ onModalChange,
24
+ onSchemaChange,
25
+ resetIndices,
26
+ pageIndex,
27
+ schema,
28
+ showModal,
29
+ }) => {
30
+ const { t } = useTranslation();
31
+
32
+ const deletePage = (pageIndex) => {
33
+ try {
34
+ schema.pages.splice(pageIndex, 1);
35
+
36
+ onSchemaChange({ ...schema });
37
+ resetIndices();
38
+
39
+ showToast({
40
+ title: t("success", "Success!"),
41
+ kind: "success",
42
+ critical: true,
43
+ description: t("pageDeleted", "Page deleted"),
44
+ });
45
+ } catch (error) {
46
+ showNotification({
47
+ title: t("errorDeletingPage", "Error deleting page"),
48
+ kind: "error",
49
+ critical: true,
50
+ description: error?.message,
51
+ });
52
+ }
53
+ };
54
+
55
+ return (
56
+ <ComposedModal open={showModal} onClose={() => onModalChange(false)}>
57
+ <ModalHeader
58
+ title={t(
59
+ "deletePageConfirmation",
60
+ "Are you sure you want to delete this page?"
61
+ )}
62
+ />
63
+ <ModalBody>
64
+ <p>
65
+ {t(
66
+ "deletePageExplainerText",
67
+ "Deleting this page will delete all the sections and questions associated with it. This action cannot be undone."
68
+ )}
69
+ </p>
70
+ </ModalBody>
71
+ <ModalFooter>
72
+ <Button kind="secondary" onClick={() => onModalChange(false)}>
73
+ {t("cancel", "Cancel")}
74
+ </Button>
75
+ <Button
76
+ kind="danger"
77
+ onClick={() => {
78
+ deletePage(pageIndex);
79
+ onModalChange(false);
80
+ }}
81
+ >
82
+ <span>{t("deletePage", "Delete page")}</span>
83
+ </Button>
84
+ </ModalFooter>
85
+ </ComposedModal>
86
+ );
87
+ };
88
+
89
+ export default DeletePageModal;