@openmrs/esm-form-builder-app 1.0.1-pre.126
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/.eslintignore +2 -0
- package/.eslintrc +33 -0
- package/.husky/pre-commit +4 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +14 -0
- package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +541 -0
- package/.yarn/plugins/@yarnpkg/plugin-version.cjs +550 -0
- package/.yarn/versions/7d315ef1.yml +0 -0
- package/LICENSE +401 -0
- package/README.md +14 -0
- package/__mocks__/react-i18next.js +56 -0
- package/dist/openmrs-esm-form-builder-app.js +1 -0
- package/i18next-parser.config.js +89 -0
- package/jest.config.json +19 -0
- package/package.json +102 -0
- package/src/components/dashboard/dashboard.component.tsx +310 -0
- package/src/components/dashboard/dashboard.scss +112 -0
- package/src/components/empty-state/empty-data-illustration.component.tsx +51 -0
- package/src/components/empty-state/empty-state.component.tsx +41 -0
- package/src/components/empty-state/empty-state.scss +55 -0
- package/src/components/error-state/error-state.component.tsx +37 -0
- package/src/components/error-state/error-state.scss +49 -0
- package/src/components/form-editor/form-editor.component.tsx +297 -0
- package/src/components/form-editor/form-editor.scss +50 -0
- package/src/components/form-renderer/form-renderer.component.tsx +82 -0
- package/src/components/form-renderer/form-renderer.scss +31 -0
- package/src/components/interactive-builder/add-question-modal.component.tsx +494 -0
- package/src/components/interactive-builder/edit-question-modal.component.tsx +447 -0
- package/src/components/interactive-builder/editable-value.component.tsx +60 -0
- package/src/components/interactive-builder/editable-value.scss +23 -0
- package/src/components/interactive-builder/interactive-builder.component.tsx +403 -0
- package/src/components/interactive-builder/interactive-builder.scss +83 -0
- package/src/components/interactive-builder/new-form-modal.component.tsx +86 -0
- package/src/components/interactive-builder/page-modal.component.tsx +91 -0
- package/src/components/interactive-builder/question-modal.scss +35 -0
- package/src/components/interactive-builder/section-modal.component.tsx +94 -0
- package/src/components/interactive-builder/value-editor.component.tsx +55 -0
- package/src/components/interactive-builder/value-editor.scss +10 -0
- package/src/components/modals/save-form.component.tsx +310 -0
- package/src/components/modals/save-form.scss +5 -0
- package/src/components/schema-editor/schema-editor.component.tsx +190 -0
- package/src/components/schema-editor/schema-editor.scss +30 -0
- package/src/config-schema.ts +47 -0
- package/src/constants.ts +3 -0
- package/src/declarations.d.tsx +2 -0
- package/src/form-builder-app-menu-link.component.tsx +13 -0
- package/src/forms.resource.ts +178 -0
- package/src/hooks/useClobdata.ts +20 -0
- package/src/hooks/useConceptLookup.ts +18 -0
- package/src/hooks/useEncounterTypes.ts +18 -0
- package/src/hooks/useForm.ts +18 -0
- package/src/hooks/useForms.ts +20 -0
- package/src/index.ts +70 -0
- package/src/root.component.tsx +19 -0
- package/src/setup-tests.ts +1 -0
- package/src/types.ts +132 -0
- package/translations/en.json +110 -0
- package/tsconfig.json +23 -0
- package/turbo.json +26 -0
- package/webpack.config.js +19 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Layer, Tile } from "@carbon/react";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import { useLayoutType } from "@openmrs/esm-framework";
|
|
5
|
+
import styles from "./error-state.scss";
|
|
6
|
+
|
|
7
|
+
interface ErrorStateProps {
|
|
8
|
+
error: Error;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ErrorState: React.FC<ErrorStateProps> = ({ error }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const isTablet = useLayoutType() === "tablet";
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Layer>
|
|
17
|
+
<Tile className={styles.tile}>
|
|
18
|
+
<div
|
|
19
|
+
className={isTablet ? styles.tabletHeading : styles.desktopHeading}
|
|
20
|
+
>
|
|
21
|
+
<h4>{t("forms", "Forms")}</h4>
|
|
22
|
+
</div>
|
|
23
|
+
<p className={styles.errorMessage}>
|
|
24
|
+
{t("error", "Error")}: {error?.message}
|
|
25
|
+
</p>
|
|
26
|
+
<p className={styles.errorCopy}>
|
|
27
|
+
{t(
|
|
28
|
+
"errorCopy",
|
|
29
|
+
"Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above."
|
|
30
|
+
)}
|
|
31
|
+
</p>
|
|
32
|
+
</Tile>
|
|
33
|
+
</Layer>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default ErrorState;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/spacing';
|
|
2
|
+
@use '@carbon/styles/scss/type';
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.errorMessage {
|
|
6
|
+
@include type.type-style("heading-compact-02");
|
|
7
|
+
|
|
8
|
+
margin-top: 2.25rem;
|
|
9
|
+
margin-bottom: spacing.$spacing-03;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.errorCopy {
|
|
13
|
+
margin-bottom: spacing.$spacing-03;
|
|
14
|
+
@include type.type-style("body-01");
|
|
15
|
+
color: $text-02;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.desktopHeading {
|
|
19
|
+
h4 {
|
|
20
|
+
@include type.type-style('heading-compact-02');
|
|
21
|
+
color: $text-02;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.tabletHeading {
|
|
26
|
+
h4 {
|
|
27
|
+
@include type.type-style('heading-03');
|
|
28
|
+
color: $text-02;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.desktopHeading, .tabletHeading {
|
|
33
|
+
text-align: left;
|
|
34
|
+
text-transform: capitalize;
|
|
35
|
+
margin-bottom: spacing.$spacing-05;
|
|
36
|
+
|
|
37
|
+
h4:after {
|
|
38
|
+
content: "";
|
|
39
|
+
display: block;
|
|
40
|
+
width: 2rem;
|
|
41
|
+
padding-top: 0.188rem;
|
|
42
|
+
border-bottom: 0.375rem solid var(--brand-03);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.tile {
|
|
47
|
+
text-align: center;
|
|
48
|
+
border: 1px solid $ui-03;
|
|
49
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Column,
|
|
4
|
+
ComposedModal,
|
|
5
|
+
InlineLoading,
|
|
6
|
+
InlineNotification,
|
|
7
|
+
Grid,
|
|
8
|
+
ModalBody,
|
|
9
|
+
ModalFooter,
|
|
10
|
+
ModalHeader,
|
|
11
|
+
Tabs,
|
|
12
|
+
Tab,
|
|
13
|
+
TabList,
|
|
14
|
+
TabPanels,
|
|
15
|
+
TabPanel,
|
|
16
|
+
Button,
|
|
17
|
+
} from "@carbon/react";
|
|
18
|
+
import { useParams } from "react-router-dom";
|
|
19
|
+
import { useSWRConfig } from "swr";
|
|
20
|
+
import { useTranslation } from "react-i18next";
|
|
21
|
+
import SaveForm from "../modals/save-form.component";
|
|
22
|
+
import {
|
|
23
|
+
showToast,
|
|
24
|
+
ExtensionSlot,
|
|
25
|
+
showNotification,
|
|
26
|
+
} from "@openmrs/esm-framework";
|
|
27
|
+
import { Schema, RouteParams } from "../../types";
|
|
28
|
+
import { useClobdata } from "../../hooks/useClobdata";
|
|
29
|
+
import { useForm } from "../../hooks/useForm";
|
|
30
|
+
import { publishForm, unpublishForm } from "../../forms.resource";
|
|
31
|
+
import FormRenderer from "../form-renderer/form-renderer.component";
|
|
32
|
+
import SchemaEditor from "../schema-editor/schema-editor.component";
|
|
33
|
+
import styles from "./form-editor.scss";
|
|
34
|
+
import InteractiveBuilder from "../interactive-builder/interactive-builder.component";
|
|
35
|
+
|
|
36
|
+
const Error = ({ error, title }) => {
|
|
37
|
+
return (
|
|
38
|
+
<InlineNotification
|
|
39
|
+
style={{
|
|
40
|
+
minWidth: "100%",
|
|
41
|
+
margin: "0rem",
|
|
42
|
+
padding: "0rem",
|
|
43
|
+
}}
|
|
44
|
+
kind={"error"}
|
|
45
|
+
lowContrast
|
|
46
|
+
subtitle={error?.message}
|
|
47
|
+
title={title}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const FormEditor: React.FC = () => {
|
|
53
|
+
const { t } = useTranslation();
|
|
54
|
+
const { formUuid } = useParams<RouteParams>();
|
|
55
|
+
const [schema, setSchema] = useState<Schema>(undefined);
|
|
56
|
+
const [isPublishing, setIsPublishing] = useState(false);
|
|
57
|
+
const [isUnpublishing, setIsUnpublishing] = useState(false);
|
|
58
|
+
const [showUnpublishModal, setShowUnpublishModal] = useState(false);
|
|
59
|
+
const { form, formError, isLoadingForm } = useForm(formUuid);
|
|
60
|
+
const { clobdata, clobdataError, isLoadingClobdata } = useClobdata(form);
|
|
61
|
+
const { cache, mutate }: { cache: any; mutate: Function } = useSWRConfig();
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (clobdata) {
|
|
65
|
+
setSchema(clobdata);
|
|
66
|
+
}
|
|
67
|
+
}, [clobdata, setSchema]);
|
|
68
|
+
|
|
69
|
+
const updateSchema = useCallback((updatedSchema) => {
|
|
70
|
+
setSchema(updatedSchema);
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
const revalidate = () => {
|
|
74
|
+
const apiUrlPattern = new RegExp("\\/ws\\/rest\\/v1\\/form");
|
|
75
|
+
|
|
76
|
+
// Find matching keys from SWR's cache and broadcast a revalidation message to their pre-bound SWR hooks
|
|
77
|
+
Array.from(cache.keys())
|
|
78
|
+
.filter((url: string) => apiUrlPattern.test(url))
|
|
79
|
+
.forEach((url: string) => mutate(url));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const launchUnpublishModal = () => {
|
|
83
|
+
setShowUnpublishModal(true);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
async function handlePublish() {
|
|
87
|
+
setIsPublishing(true);
|
|
88
|
+
try {
|
|
89
|
+
await publishForm(form.uuid);
|
|
90
|
+
|
|
91
|
+
showToast({
|
|
92
|
+
title: t("formPublished", "Form published"),
|
|
93
|
+
kind: "success",
|
|
94
|
+
critical: true,
|
|
95
|
+
description:
|
|
96
|
+
`${form.name} ` +
|
|
97
|
+
t("formPublishedSuccessfully", "form was published successfully"),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
revalidate();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
showNotification({
|
|
103
|
+
title: t("errorPublishingForm", "Error publishing form"),
|
|
104
|
+
kind: "error",
|
|
105
|
+
critical: true,
|
|
106
|
+
description: error?.message,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
setIsPublishing(false);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function handleUnpublish() {
|
|
113
|
+
setIsUnpublishing(true);
|
|
114
|
+
try {
|
|
115
|
+
await unpublishForm(form.uuid);
|
|
116
|
+
|
|
117
|
+
showToast({
|
|
118
|
+
title: t("formUnpublished", "Form unpublished"),
|
|
119
|
+
kind: "success",
|
|
120
|
+
critical: true,
|
|
121
|
+
description:
|
|
122
|
+
`${form.name} ` +
|
|
123
|
+
t("formUnpublishedSuccessfully", "form was unpublished successfully"),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
revalidate();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
showNotification({
|
|
129
|
+
title: t("errorUnpublishingForm", "Error unpublishing form"),
|
|
130
|
+
kind: "error",
|
|
131
|
+
critical: true,
|
|
132
|
+
description: error?.message,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
setIsUnpublishing(false);
|
|
136
|
+
setShowUnpublishModal(false);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (!isLoadingClobdata) {
|
|
141
|
+
setSchema(clobdata);
|
|
142
|
+
}
|
|
143
|
+
}, [clobdata, isLoadingClobdata]);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
<div className={styles.breadcrumbsContainer}>
|
|
148
|
+
<ExtensionSlot extensionSlotName="breadcrumbs-slot" />
|
|
149
|
+
</div>
|
|
150
|
+
<div className={styles.container}>
|
|
151
|
+
<Grid className={styles.grid}>
|
|
152
|
+
<Column lg={8} md={8} className={styles.column}>
|
|
153
|
+
<Tabs>
|
|
154
|
+
<TabList>
|
|
155
|
+
<Tab>{t("schemaEditor", "Schema Editor")}</Tab>
|
|
156
|
+
<Tab>{t("interactiveBuilder", "Interactive Builder")}</Tab>
|
|
157
|
+
</TabList>
|
|
158
|
+
<TabPanels>
|
|
159
|
+
<TabPanel>
|
|
160
|
+
<>
|
|
161
|
+
{formError ? (
|
|
162
|
+
<Error
|
|
163
|
+
error={formError}
|
|
164
|
+
title={t("formError", "Error loading form metadata")}
|
|
165
|
+
/>
|
|
166
|
+
) : null}
|
|
167
|
+
{clobdataError ? (
|
|
168
|
+
<Error
|
|
169
|
+
error={clobdataError}
|
|
170
|
+
title={t("schemaLoadError", "Error loading schema")}
|
|
171
|
+
/>
|
|
172
|
+
) : null}
|
|
173
|
+
<SchemaEditor
|
|
174
|
+
schema={schema}
|
|
175
|
+
onSchemaChange={updateSchema}
|
|
176
|
+
isLoading={
|
|
177
|
+
formUuid && (isLoadingClobdata || isLoadingForm)
|
|
178
|
+
}
|
|
179
|
+
/>
|
|
180
|
+
</>
|
|
181
|
+
</TabPanel>
|
|
182
|
+
<TabPanel>
|
|
183
|
+
<InteractiveBuilder
|
|
184
|
+
schema={schema}
|
|
185
|
+
onSchemaChange={updateSchema}
|
|
186
|
+
isLoading={formUuid && (isLoadingClobdata || isLoadingForm)}
|
|
187
|
+
/>
|
|
188
|
+
</TabPanel>
|
|
189
|
+
</TabPanels>
|
|
190
|
+
</Tabs>
|
|
191
|
+
</Column>
|
|
192
|
+
<Column lg={8} md={8} className={styles.column}>
|
|
193
|
+
<Tabs>
|
|
194
|
+
<TabList>
|
|
195
|
+
<Tab>{t("preview", "Preview")}</Tab>
|
|
196
|
+
</TabList>
|
|
197
|
+
<TabPanels>
|
|
198
|
+
<TabPanel>
|
|
199
|
+
<>
|
|
200
|
+
<div className={styles.actionButtons}>
|
|
201
|
+
<SaveForm form={form} schema={schema} />
|
|
202
|
+
|
|
203
|
+
<>
|
|
204
|
+
{form && !form.published ? (
|
|
205
|
+
<Button
|
|
206
|
+
kind="secondary"
|
|
207
|
+
onClick={handlePublish}
|
|
208
|
+
disabled={isPublishing}
|
|
209
|
+
>
|
|
210
|
+
{isPublishing && !form?.published ? (
|
|
211
|
+
<InlineLoading
|
|
212
|
+
className={styles.spinner}
|
|
213
|
+
description={
|
|
214
|
+
t("publishing", "Publishing") + "..."
|
|
215
|
+
}
|
|
216
|
+
/>
|
|
217
|
+
) : (
|
|
218
|
+
<span>{t("publishForm", "Publish form")}</span>
|
|
219
|
+
)}
|
|
220
|
+
</Button>
|
|
221
|
+
) : null}
|
|
222
|
+
{form && form.published ? (
|
|
223
|
+
<Button
|
|
224
|
+
kind="danger"
|
|
225
|
+
onClick={launchUnpublishModal}
|
|
226
|
+
disabled={isUnpublishing}
|
|
227
|
+
>
|
|
228
|
+
{t("unpublishForm", "Unpublish form")}
|
|
229
|
+
</Button>
|
|
230
|
+
) : null}
|
|
231
|
+
{showUnpublishModal ? (
|
|
232
|
+
<ComposedModal
|
|
233
|
+
open={true}
|
|
234
|
+
onClose={() => setShowUnpublishModal(false)}
|
|
235
|
+
>
|
|
236
|
+
<ModalHeader
|
|
237
|
+
title={t(
|
|
238
|
+
"unpublishConfirmation",
|
|
239
|
+
"Are you sure you want to unpublish this form?"
|
|
240
|
+
)}
|
|
241
|
+
></ModalHeader>
|
|
242
|
+
<ModalBody>
|
|
243
|
+
<p>
|
|
244
|
+
{t(
|
|
245
|
+
"unpublishExplainerText",
|
|
246
|
+
"Unpublishing a form means you can no longer access it from your frontend. Unpublishing forms does not delete their associated schemas, it only affects whether or not you can access them in your frontend."
|
|
247
|
+
)}
|
|
248
|
+
</p>
|
|
249
|
+
</ModalBody>
|
|
250
|
+
<ModalFooter>
|
|
251
|
+
<Button
|
|
252
|
+
kind="secondary"
|
|
253
|
+
onClick={() => setShowUnpublishModal(false)}
|
|
254
|
+
>
|
|
255
|
+
{t("cancel", "Cancel")}
|
|
256
|
+
</Button>
|
|
257
|
+
<Button
|
|
258
|
+
disabled={isUnpublishing}
|
|
259
|
+
kind={isUnpublishing ? "secondary" : "danger"}
|
|
260
|
+
onClick={handleUnpublish}
|
|
261
|
+
>
|
|
262
|
+
{isUnpublishing ? (
|
|
263
|
+
<InlineLoading
|
|
264
|
+
className={styles.spinner}
|
|
265
|
+
description={
|
|
266
|
+
t("unpublishing", "Unpublishing") + "..."
|
|
267
|
+
}
|
|
268
|
+
/>
|
|
269
|
+
) : (
|
|
270
|
+
<span>
|
|
271
|
+
{t("unpublishForm", "Unpublish form")}
|
|
272
|
+
</span>
|
|
273
|
+
)}
|
|
274
|
+
</Button>
|
|
275
|
+
</ModalFooter>
|
|
276
|
+
</ComposedModal>
|
|
277
|
+
) : (
|
|
278
|
+
false
|
|
279
|
+
)}
|
|
280
|
+
</>
|
|
281
|
+
</div>
|
|
282
|
+
<FormRenderer
|
|
283
|
+
schema={schema}
|
|
284
|
+
onSchemaChange={updateSchema}
|
|
285
|
+
/>
|
|
286
|
+
</>
|
|
287
|
+
</TabPanel>
|
|
288
|
+
</TabPanels>
|
|
289
|
+
</Tabs>
|
|
290
|
+
</Column>
|
|
291
|
+
</Grid>
|
|
292
|
+
</div>
|
|
293
|
+
</>
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
export default FormEditor;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
@use "@carbon/styles/scss/spacing";
|
|
2
|
+
@use "@carbon/styles/scss/type";
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
padding: 2rem;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.breadcrumbsContainer {
|
|
12
|
+
& > .nav.breadcrumbs-container {
|
|
13
|
+
background-color: $ui-03;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.grid {
|
|
18
|
+
margin-left: 0;
|
|
19
|
+
margin-right: 0;
|
|
20
|
+
padding-left: 0;
|
|
21
|
+
padding-right: 0;
|
|
22
|
+
max-width: 100%;
|
|
23
|
+
|
|
24
|
+
:global(.cds--tabs__nav-item--selected) {
|
|
25
|
+
border-bottom: 2px solid var(--cds-border-interactive, #005d5d);
|
|
26
|
+
outline: none !important;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.column {
|
|
31
|
+
margin-left: 0;
|
|
32
|
+
margin-right: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.actionButtons {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: flex-end;
|
|
39
|
+
margin: 1rem 0;
|
|
40
|
+
|
|
41
|
+
button {
|
|
42
|
+
margin-left: 1rem
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.spinner {
|
|
47
|
+
&:global(.cds--inline-loading) {
|
|
48
|
+
min-height: 1rem;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { OHRIFormSchema, OHRIForm } from "@ohri/openmrs-ohri-form-engine-lib";
|
|
4
|
+
import { Tile } from "@carbon/react";
|
|
5
|
+
import { useConfig } from "@openmrs/esm-framework";
|
|
6
|
+
import styles from "./form-renderer.scss";
|
|
7
|
+
|
|
8
|
+
type FormRendererProps = {
|
|
9
|
+
onSchemaChange?: (schema: OHRIFormSchema) => void;
|
|
10
|
+
schema: OHRIFormSchema;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const FormRenderer: React.FC<FormRendererProps> = ({ schema }) => {
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const { patientUuid } = useConfig();
|
|
16
|
+
|
|
17
|
+
const dummySchema: OHRIFormSchema = {
|
|
18
|
+
encounterType: "",
|
|
19
|
+
name: "Test Form",
|
|
20
|
+
pages: [
|
|
21
|
+
{
|
|
22
|
+
label: "Test Page",
|
|
23
|
+
sections: [
|
|
24
|
+
{
|
|
25
|
+
label: "Test Section",
|
|
26
|
+
isExpanded: "true",
|
|
27
|
+
questions: [
|
|
28
|
+
{
|
|
29
|
+
label: "Test Question",
|
|
30
|
+
type: "obs",
|
|
31
|
+
questionOptions: {
|
|
32
|
+
rendering: "text",
|
|
33
|
+
concept: "xxxx",
|
|
34
|
+
},
|
|
35
|
+
id: "testQuestion",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
processor: "EncounterFormProcessor",
|
|
43
|
+
referencedForms: [],
|
|
44
|
+
uuid: "xxx",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const [schemaToRender, setSchemaToRender] =
|
|
48
|
+
useState<OHRIFormSchema>(dummySchema);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (schema) {
|
|
52
|
+
setSchemaToRender(schema);
|
|
53
|
+
}
|
|
54
|
+
}, [schema]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className={styles.container}>
|
|
58
|
+
{!schema && (
|
|
59
|
+
<Tile className={styles.emptyStateTile}>
|
|
60
|
+
<h4 className={styles.heading}>
|
|
61
|
+
{t("noSchemaLoaded", "No schema loaded")}
|
|
62
|
+
</h4>
|
|
63
|
+
<p className={styles.helperText}>
|
|
64
|
+
{t(
|
|
65
|
+
"formRendererHelperText",
|
|
66
|
+
"Load a form schema in the Schema Editor to the left to see it rendered here by the Form Engine."
|
|
67
|
+
)}
|
|
68
|
+
</p>
|
|
69
|
+
</Tile>
|
|
70
|
+
)}
|
|
71
|
+
{schema === schemaToRender && (
|
|
72
|
+
<OHRIForm
|
|
73
|
+
formJson={schemaToRender}
|
|
74
|
+
mode={"enter"}
|
|
75
|
+
patientUUID={patientUuid}
|
|
76
|
+
/>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default FormRenderer;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/colors';
|
|
2
|
+
@use '@carbon/styles/scss/spacing';
|
|
3
|
+
@use '@carbon/styles/scss/type';
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
background-color: white;
|
|
7
|
+
padding: 1rem;
|
|
8
|
+
overflow-y: scroll;
|
|
9
|
+
height: 100vh;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.emptyStateTile {
|
|
13
|
+
width: 80%;
|
|
14
|
+
margin: 1.25rem auto;
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-flow: column wrap;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
padding: 1.5rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.heading {
|
|
23
|
+
@include type.type-style('heading-compact-02');
|
|
24
|
+
color: colors.$gray-100;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.helperText {
|
|
28
|
+
margin-top: 0.5rem;
|
|
29
|
+
@include type.type-style('body-01');
|
|
30
|
+
color: colors.$gray-90;
|
|
31
|
+
}
|