@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,100 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
3
+
4
+ .container {
5
+ :global(.cds--modal-content:focus) {
6
+ outline: none;
7
+ }
8
+
9
+ :global(.cds--tooltip) {
10
+ z-index: 1;
11
+ }
12
+ }
13
+
14
+ .helperText {
15
+ @include type.type-style('body-compact-01');
16
+ margin: 1rem 0rem;
17
+ }
18
+
19
+ .heading {
20
+ @include type.type-style('heading-03');
21
+ margin: 1rem 0rem 0.5rem;
22
+ }
23
+
24
+ .styledHeading {
25
+ @include type.type-style('heading-02');
26
+ margin: 0.5rem 0rem;
27
+
28
+ &:after {
29
+ content: "";
30
+ display: block;
31
+ width: 2rem;
32
+ padding-top: 0.188rem;
33
+ border-bottom: 0.375rem solid var(--brand-03);
34
+ }
35
+ }
36
+
37
+ .subheading {
38
+ @extend .heading;
39
+ @include type.type-style('heading-02');
40
+ }
41
+
42
+ .name {
43
+ @extend .heading;
44
+ @include type.type-style('heading-02');
45
+ }
46
+
47
+ .editableFieldsContainer {
48
+ margin: 1rem 0;
49
+ border-bottom: 1px solid $ui-03;
50
+ }
51
+
52
+ .addQuestionButton {
53
+ margin: 0.75rem 0;
54
+ }
55
+
56
+ .header {
57
+ margin: 2rem 0;
58
+ }
59
+
60
+ .explainer {
61
+ margin-bottom: 2rem;
62
+
63
+ p {
64
+ margin: 1.5rem 0;
65
+
66
+ &:first-of-type {
67
+ @include type.type-style('heading-04');
68
+ }
69
+ }
70
+ }
71
+
72
+ .sectionExplainer {
73
+ margin: 1.5rem 0;
74
+ }
75
+
76
+ .startButton {
77
+ margin: 1rem 0;
78
+ }
79
+
80
+ .editorContainer {
81
+ display: flex;
82
+ justify-content: space-between;
83
+ margin: 0.25rem 0.5rem;
84
+ align-items: center;
85
+ width: 100%;
86
+ }
87
+
88
+ .buttonContainer {
89
+ span {
90
+ margin: 0rem 0.5rem;
91
+ }
92
+ }
93
+
94
+ .questionLabel {
95
+ @include type.type-style('body-01');
96
+ }
97
+
98
+ .addSectionButton {
99
+ margin: 1.5rem 0;
100
+ }
@@ -0,0 +1,86 @@
1
+ import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ Form,
7
+ FormGroup,
8
+ ModalBody,
9
+ ModalFooter,
10
+ ModalHeader,
11
+ TextInput,
12
+ } from "@carbon/react";
13
+ import { showToast, showNotification } from "@openmrs/esm-framework";
14
+ import { Schema } from "../../types";
15
+
16
+ type NewFormModalProps = {
17
+ schema: Schema;
18
+ onSchemaChange: (schema: Schema) => void;
19
+ showModal: boolean;
20
+ onModalChange: (showModal: boolean) => void;
21
+ };
22
+
23
+ const NewFormModal: React.FC<NewFormModalProps> = ({
24
+ schema,
25
+ onSchemaChange,
26
+ showModal,
27
+ onModalChange,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const [formName, setFormName] = useState("");
31
+
32
+ const updateFormName = () => {
33
+ try {
34
+ schema.name = formName;
35
+ onSchemaChange({ ...schema });
36
+
37
+ showToast({
38
+ title: t("success", "Success!"),
39
+ kind: "success",
40
+ critical: true,
41
+ description: t("formCreated", "New form created"),
42
+ });
43
+ } catch (error) {
44
+ showNotification({
45
+ title: t("errorCreatingForm", "Error creating form"),
46
+ kind: "error",
47
+ critical: true,
48
+ description: error?.message,
49
+ });
50
+ }
51
+ };
52
+
53
+ return (
54
+ <ComposedModal open={showModal} onClose={() => onModalChange(false)}>
55
+ <ModalHeader title={t("createNewForm", "Create a new form")} />
56
+ <Form onSubmit={(event) => event.preventDefault()}>
57
+ <ModalBody>
58
+ <FormGroup legendText={""}>
59
+ <TextInput
60
+ id="formName"
61
+ labelText={t("formName", "Form name")}
62
+ value={formName}
63
+ onChange={(event) => setFormName(event.target.value)}
64
+ />
65
+ </FormGroup>
66
+ </ModalBody>
67
+ </Form>
68
+ <ModalFooter>
69
+ <Button kind="secondary" onClick={() => onModalChange(false)}>
70
+ {t("cancel", "Cancel")}
71
+ </Button>
72
+ <Button
73
+ disabled={!formName}
74
+ onClick={() => {
75
+ updateFormName();
76
+ onModalChange(false);
77
+ }}
78
+ >
79
+ <span>{t("createForm", "Create Form")}</span>
80
+ </Button>
81
+ </ModalFooter>
82
+ </ComposedModal>
83
+ );
84
+ };
85
+
86
+ export default NewFormModal;
@@ -0,0 +1,91 @@
1
+ import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ Form,
7
+ FormGroup,
8
+ ModalBody,
9
+ ModalFooter,
10
+ ModalHeader,
11
+ TextInput,
12
+ } from "@carbon/react";
13
+ import { showToast, showNotification } from "@openmrs/esm-framework";
14
+ import { Schema } from "../../types";
15
+
16
+ type PageModalProps = {
17
+ schema: Schema;
18
+ onSchemaChange: (schema: Schema) => void;
19
+ showModal: boolean;
20
+ onModalChange: (showModal: boolean) => void;
21
+ };
22
+
23
+ const PageModal: React.FC<PageModalProps> = ({
24
+ schema,
25
+ onSchemaChange,
26
+ showModal,
27
+ onModalChange,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const [pageTitle, setPageTitle] = useState("");
31
+
32
+ const handleUpdatePageTitle = () => {
33
+ updatePages();
34
+ onModalChange(false);
35
+ };
36
+
37
+ const updatePages = () => {
38
+ try {
39
+ if (pageTitle) {
40
+ schema.pages.push({
41
+ label: pageTitle,
42
+ sections: [],
43
+ });
44
+
45
+ onSchemaChange({ ...schema });
46
+ setPageTitle("");
47
+ }
48
+ showToast({
49
+ title: t("success", "Success!"),
50
+ kind: "success",
51
+ critical: true,
52
+ description: t("pageCreated", "New page created"),
53
+ });
54
+ } catch (error) {
55
+ showNotification({
56
+ title: t("errorCreatingPage", "Error creating page"),
57
+ kind: "error",
58
+ critical: true,
59
+ description: error?.message,
60
+ });
61
+ }
62
+ };
63
+
64
+ return (
65
+ <ComposedModal open={showModal} onClose={() => onModalChange(false)}>
66
+ <ModalHeader title={t("createNewPage", "Create a new page")} />
67
+ <Form onSubmit={(event) => event.preventDefault()}>
68
+ <ModalBody>
69
+ <FormGroup legendText={""}>
70
+ <TextInput
71
+ id="pageTitle"
72
+ labelText={t("enterPageTitle", "Enter a title for your new page")}
73
+ value={pageTitle}
74
+ onChange={(event) => setPageTitle(event.target.value)}
75
+ />
76
+ </FormGroup>
77
+ </ModalBody>
78
+ </Form>
79
+ <ModalFooter>
80
+ <Button onClick={() => onModalChange(false)} kind="secondary">
81
+ {t("cancel", "Cancel")}
82
+ </Button>
83
+ <Button disabled={!pageTitle} onClick={handleUpdatePageTitle}>
84
+ <span>{t("save", "Save")}</span>
85
+ </Button>
86
+ </ModalFooter>
87
+ </ComposedModal>
88
+ );
89
+ };
90
+
91
+ export default PageModal;
@@ -0,0 +1,35 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
3
+
4
+ .label {
5
+ margin-bottom: 0.5rem;
6
+ }
7
+
8
+ .loader {
9
+ padding: 1rem 0.5rem;
10
+ }
11
+
12
+ .tag {
13
+ margin-right: 0.5rem;
14
+ }
15
+
16
+ .conceptList {
17
+ max-height: 14rem;
18
+ overflow-y: auto;
19
+ border: 1px solid $ui-03;
20
+ border-top: none;
21
+ }
22
+
23
+ .conceptList li:hover {
24
+ background-color: $ui-03;
25
+ }
26
+
27
+ .concept {
28
+ padding: 0.75rem;
29
+ }
30
+
31
+ .emptyResults {
32
+ @include type.type-style("body-compact-01");
33
+ color: $text-02;
34
+ min-height: 1rem;
35
+ }
@@ -0,0 +1,94 @@
1
+ import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ ComposedModal,
6
+ Form,
7
+ FormGroup,
8
+ ModalBody,
9
+ ModalFooter,
10
+ ModalHeader,
11
+ TextInput,
12
+ } from "@carbon/react";
13
+ import { showToast, showNotification } from "@openmrs/esm-framework";
14
+ import { Schema } from "../../types";
15
+
16
+ type SectionModalProps = {
17
+ schema: Schema;
18
+ onSchemaChange: (schema: Schema) => void;
19
+ pageIndex: number;
20
+ resetIndices: () => void;
21
+ showModal: boolean;
22
+ onModalChange: (showModal: boolean) => void;
23
+ };
24
+
25
+ const SectionModal: React.FC<SectionModalProps> = ({
26
+ schema,
27
+ onSchemaChange,
28
+ pageIndex,
29
+ resetIndices,
30
+ showModal,
31
+ onModalChange,
32
+ }) => {
33
+ const { t } = useTranslation();
34
+ const [sectionTitle, setSectionTitle] = useState("");
35
+
36
+ const handleUpdatePageSections = () => {
37
+ updateSections();
38
+ onModalChange(false);
39
+ };
40
+
41
+ const updateSections = () => {
42
+ try {
43
+ schema.pages[pageIndex]?.sections?.push({
44
+ label: sectionTitle,
45
+ isExpanded: "true",
46
+ questions: [],
47
+ });
48
+ onSchemaChange({ ...schema });
49
+ setSectionTitle("");
50
+ resetIndices();
51
+
52
+ showToast({
53
+ title: t("success", "Success!"),
54
+ kind: "success",
55
+ critical: true,
56
+ description: t("sectionCreated", "New section created"),
57
+ });
58
+ } catch (error) {
59
+ showNotification({
60
+ title: t("errorCreatingSection", "Error creating section"),
61
+ kind: "error",
62
+ critical: true,
63
+ description: error?.message,
64
+ });
65
+ }
66
+ };
67
+
68
+ return (
69
+ <ComposedModal open={showModal} onClose={() => onModalChange(false)}>
70
+ <ModalHeader title={t("createNewSection", "Create a new section")} />
71
+ <Form onSubmit={(event) => event.preventDefault()}>
72
+ <ModalBody>
73
+ <FormGroup legendText={""}>
74
+ <TextInput
75
+ id="sectionTitle"
76
+ labelText={t("enterSectionTitle", "Enter a section title")}
77
+ value={sectionTitle}
78
+ onChange={(event) => setSectionTitle(event.target.value)}
79
+ />
80
+ </FormGroup>
81
+ </ModalBody>
82
+ </Form>
83
+ <ModalFooter>
84
+ <Button onClick={() => onModalChange(false)} kind="secondary">
85
+ {t("cancel", "Cancel")}
86
+ </Button>
87
+ <Button disabled={!sectionTitle} onClick={handleUpdatePageSections}>
88
+ <span>{t("save", "Save")}</span>
89
+ </Button>
90
+ </ModalFooter>
91
+ </ComposedModal>
92
+ );
93
+ };
94
+ export default SectionModal;
@@ -0,0 +1,55 @@
1
+ import React, { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import { Button, TextInput } from "@carbon/react";
4
+ import { Close, Save } from "@carbon/react/icons";
5
+ import styles from "./value-editor.scss";
6
+
7
+ type ValueEditorProps = {
8
+ id: string;
9
+ handleCancel: () => void;
10
+ handleSave: (value: string) => void;
11
+ onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
12
+ value: string;
13
+ };
14
+
15
+ const ValueEditor: React.FC<ValueEditorProps> = ({
16
+ id,
17
+ handleCancel,
18
+ handleSave,
19
+ onChange,
20
+ value,
21
+ }) => {
22
+ const { t } = useTranslation();
23
+ const [tmpValue, setTmpValue] = useState(value);
24
+
25
+ return (
26
+ <>
27
+ <TextInput
28
+ id={id}
29
+ labelText=""
30
+ value={tmpValue}
31
+ onChange={(event) => setTmpValue(event.target.value)}
32
+ />
33
+ <div className={styles.actionButtons}>
34
+ <Button
35
+ size="md"
36
+ renderIcon={(props) => <Save {...props} size={16} />}
37
+ kind="primary"
38
+ onClick={() => handleSave(tmpValue)}
39
+ >
40
+ {t("saveButtonText", "Save")}
41
+ </Button>
42
+ <Button
43
+ size="md"
44
+ renderIcon={(props) => <Close {...props} size={16} />}
45
+ kind="secondary"
46
+ onClick={handleCancel}
47
+ >
48
+ {t("cancelButtonText", "Cancel")}
49
+ </Button>
50
+ </div>
51
+ </>
52
+ );
53
+ };
54
+
55
+ export default ValueEditor;
@@ -0,0 +1,10 @@
1
+ .actionButtons {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: flex-end;
5
+ margin: 1rem 0;
6
+
7
+ button {
8
+ margin-left: 1rem
9
+ }
10
+ }