@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.
Files changed (60) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +33 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.husky/pre-push +6 -0
  5. package/.prettierignore +14 -0
  6. package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +541 -0
  7. package/.yarn/plugins/@yarnpkg/plugin-version.cjs +550 -0
  8. package/.yarn/versions/7d315ef1.yml +0 -0
  9. package/LICENSE +401 -0
  10. package/README.md +14 -0
  11. package/__mocks__/react-i18next.js +56 -0
  12. package/dist/openmrs-esm-form-builder-app.js +1 -0
  13. package/i18next-parser.config.js +89 -0
  14. package/jest.config.json +19 -0
  15. package/package.json +102 -0
  16. package/src/components/dashboard/dashboard.component.tsx +310 -0
  17. package/src/components/dashboard/dashboard.scss +112 -0
  18. package/src/components/empty-state/empty-data-illustration.component.tsx +51 -0
  19. package/src/components/empty-state/empty-state.component.tsx +41 -0
  20. package/src/components/empty-state/empty-state.scss +55 -0
  21. package/src/components/error-state/error-state.component.tsx +37 -0
  22. package/src/components/error-state/error-state.scss +49 -0
  23. package/src/components/form-editor/form-editor.component.tsx +297 -0
  24. package/src/components/form-editor/form-editor.scss +50 -0
  25. package/src/components/form-renderer/form-renderer.component.tsx +82 -0
  26. package/src/components/form-renderer/form-renderer.scss +31 -0
  27. package/src/components/interactive-builder/add-question-modal.component.tsx +494 -0
  28. package/src/components/interactive-builder/edit-question-modal.component.tsx +447 -0
  29. package/src/components/interactive-builder/editable-value.component.tsx +60 -0
  30. package/src/components/interactive-builder/editable-value.scss +23 -0
  31. package/src/components/interactive-builder/interactive-builder.component.tsx +403 -0
  32. package/src/components/interactive-builder/interactive-builder.scss +83 -0
  33. package/src/components/interactive-builder/new-form-modal.component.tsx +86 -0
  34. package/src/components/interactive-builder/page-modal.component.tsx +91 -0
  35. package/src/components/interactive-builder/question-modal.scss +35 -0
  36. package/src/components/interactive-builder/section-modal.component.tsx +94 -0
  37. package/src/components/interactive-builder/value-editor.component.tsx +55 -0
  38. package/src/components/interactive-builder/value-editor.scss +10 -0
  39. package/src/components/modals/save-form.component.tsx +310 -0
  40. package/src/components/modals/save-form.scss +5 -0
  41. package/src/components/schema-editor/schema-editor.component.tsx +190 -0
  42. package/src/components/schema-editor/schema-editor.scss +30 -0
  43. package/src/config-schema.ts +47 -0
  44. package/src/constants.ts +3 -0
  45. package/src/declarations.d.tsx +2 -0
  46. package/src/form-builder-app-menu-link.component.tsx +13 -0
  47. package/src/forms.resource.ts +178 -0
  48. package/src/hooks/useClobdata.ts +20 -0
  49. package/src/hooks/useConceptLookup.ts +18 -0
  50. package/src/hooks/useEncounterTypes.ts +18 -0
  51. package/src/hooks/useForm.ts +18 -0
  52. package/src/hooks/useForms.ts +20 -0
  53. package/src/index.ts +70 -0
  54. package/src/root.component.tsx +19 -0
  55. package/src/setup-tests.ts +1 -0
  56. package/src/types.ts +132 -0
  57. package/translations/en.json +110 -0
  58. package/tsconfig.json +23 -0
  59. package/turbo.json +26 -0
  60. package/webpack.config.js +19 -0
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@openmrs/esm-form-builder-app",
3
+ "version": "1.0.1-pre.126",
4
+ "license": "MPL-2.0",
5
+ "description": "OpenMRS ESM Form Builder App",
6
+ "browser": "dist/openmrs-esm-form-builder-app.js",
7
+ "main": "src/index.ts",
8
+ "source": true,
9
+ "scripts": {
10
+ "start": "openmrs develop",
11
+ "serve": "webpack serve --mode=development",
12
+ "build": "webpack --mode production",
13
+ "analyze": "webpack --mode=production --env.analyze=true",
14
+ "lint": "TIMING=1 eslint src --ext js,jsx,ts,tsx",
15
+ "prettier": "prettier --write \"src/**/*.{ts,tsx}\"",
16
+ "typescript": "tsc",
17
+ "test": "jest --config jest.config.json",
18
+ "verify": "turbo lint typescript coverage",
19
+ "coverage": "yarn test --coverage --passWithNoTests",
20
+ "prepare": "husky install",
21
+ "extract-translations": "i18next 'src/**/*.component.tsx' --config ./i18next-parser.config.js"
22
+ },
23
+ "browserslist": [
24
+ "extends browserslist-config-openmrs"
25
+ ],
26
+ "keywords": [
27
+ "openmrs",
28
+ "microfrontends",
29
+ "formbuilder",
30
+ "form builder"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/openmrs/openmrs-esm-form-builder.git"
35
+ },
36
+ "homepage": "https://github.com/openmrs/openmrs-esm-form-builder#readme",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/openmrs/openmrs-esm-form-builder/issues"
42
+ },
43
+ "dependencies": {
44
+ "@carbon/react": "^1.19.0",
45
+ "@ohri/openmrs-ohri-form-engine-lib": "next",
46
+ "file-loader": "^6.2.0",
47
+ "lodash-es": "^4.17.21",
48
+ "react-ace": "^10.1.0",
49
+ "sass": "^1.56.2"
50
+ },
51
+ "peerDependencies": {
52
+ "@openmrs/esm-framework": "*",
53
+ "dayjs": "1.x",
54
+ "react": "18.x",
55
+ "react-dom": "18.x",
56
+ "react-i18next": "11.x",
57
+ "rxjs": "6.x"
58
+ },
59
+ "devDependencies": {
60
+ "@openmrs/esm-framework": "next",
61
+ "@openmrs/esm-styleguide": "next",
62
+ "@swc/cli": "^0.1.57",
63
+ "@swc/core": "^1.3.22",
64
+ "@swc/jest": "^0.2.24",
65
+ "@testing-library/dom": "^8.19.0",
66
+ "@testing-library/jest-dom": "^5.16.5",
67
+ "@testing-library/react": "^13.4.0",
68
+ "@testing-library/user-event": "^14.4.3",
69
+ "@types/jest": "^29.2.4",
70
+ "@types/react": "^18.0.26",
71
+ "@types/react-dom": "^18.0.9",
72
+ "@types/webpack-env": "^1.18.0",
73
+ "@typescript-eslint/eslint-plugin": "^5.46.1",
74
+ "@typescript-eslint/parser": "^5.46.1",
75
+ "css-loader": "^6.7.2",
76
+ "eslint": "^8.29.0",
77
+ "eslint-config-prettier": "^8.5.0",
78
+ "eslint-config-ts-react-important-stuff": "^3.0.0",
79
+ "eslint-plugin-prettier": "^4.2.1",
80
+ "husky": "^8.0.2",
81
+ "i18next": "^19.9.2",
82
+ "i18next-parser": "^5.4.0",
83
+ "identity-obj-proxy": "^3.0.0",
84
+ "jest": "^29.3.1",
85
+ "jest-cli": "^29.3.1",
86
+ "jest-environment-jsdom": "^29.3.1",
87
+ "openmrs": "next",
88
+ "prettier": "^2.8.1",
89
+ "pretty-quick": "^3.1.3",
90
+ "react": "^18.2.0",
91
+ "react-dom": "^18.2.0",
92
+ "react-i18next": "^11.18.6",
93
+ "rxjs": "^7.6.0",
94
+ "swc-loader": "^0.2.3",
95
+ "turbo": "^1.6.3",
96
+ "typescript": "^4.9.4",
97
+ "webpack": "^5.75.0",
98
+ "webpack-cli": "^5.0.1"
99
+ },
100
+ "packageManager": "yarn@3.2.4",
101
+ "stableVersion": "1.0.0"
102
+ }
@@ -0,0 +1,310 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Button,
5
+ DataTable,
6
+ DataTableSkeleton,
7
+ Dropdown,
8
+ InlineLoading,
9
+ Table,
10
+ TableBody,
11
+ TableCell,
12
+ TableContainer,
13
+ TableHead,
14
+ TableHeader,
15
+ TableRow,
16
+ TableToolbar,
17
+ TableToolbarContent,
18
+ TableToolbarSearch,
19
+ Tag,
20
+ Tile,
21
+ } from "@carbon/react";
22
+ import { Add, DocumentImport, Download, Edit } from "@carbon/react/icons";
23
+ import { navigate, useLayoutType } from "@openmrs/esm-framework";
24
+
25
+ import { FilterProps } from "../../types";
26
+ import { useClobdata } from "../../hooks/useClobdata";
27
+ import { useForms } from "../../hooks/useForms";
28
+ import EmptyState from "../empty-state/empty-state.component";
29
+ import ErrorState from "../error-state/error-state.component";
30
+ import styles from "./dashboard.scss";
31
+
32
+ function CustomTag({ condition }) {
33
+ const { t } = useTranslation();
34
+
35
+ return condition ? (
36
+ <Tag type="green" size="md" title="Clear Filter">
37
+ {t("yes", "Yes")}
38
+ </Tag>
39
+ ) : (
40
+ <Tag type="red" size="md" title="Clear Filter">
41
+ {t("no", "No")}
42
+ </Tag>
43
+ );
44
+ }
45
+
46
+ function ActionButtons({ form }) {
47
+ const { t } = useTranslation();
48
+ const { clobdata } = useClobdata(form);
49
+ const formResources = form?.resources;
50
+
51
+ const downloadableSchema = useMemo(
52
+ () =>
53
+ new Blob([JSON.stringify(clobdata, null, 2)], {
54
+ type: "application/json",
55
+ }),
56
+ [clobdata]
57
+ );
58
+
59
+ return formResources.length == 0 || !form?.resources[0] ? (
60
+ <Button
61
+ className={styles.importButton}
62
+ renderIcon={DocumentImport}
63
+ onClick={() => navigate({ to: `form-builder/edit/${form.uuid}` })}
64
+ kind={"ghost"}
65
+ iconDescription={t("import", "Import")}
66
+ hasIconOnly
67
+ />
68
+ ) : (
69
+ <>
70
+ <Button
71
+ className={styles.editButton}
72
+ enterDelayMs={300}
73
+ renderIcon={Edit}
74
+ onClick={() =>
75
+ navigate({
76
+ to: `${window.spaBase}/form-builder/edit/${form.uuid}`,
77
+ })
78
+ }
79
+ kind={"ghost"}
80
+ iconDescription={t("editSchema", "Edit schema")}
81
+ hasIconOnly
82
+ tooltipAlignment="start"
83
+ />
84
+ <a
85
+ className={styles.downloadLink}
86
+ download={`${form?.name}.json`}
87
+ href={window.URL.createObjectURL(downloadableSchema)}
88
+ >
89
+ <Button
90
+ className={styles.downloadButton}
91
+ enterDelayMs={300}
92
+ renderIcon={Download}
93
+ kind={"ghost"}
94
+ iconDescription={t("downloadSchema", "Download schema")}
95
+ hasIconOnly
96
+ tooltipAlignment="start"
97
+ ></Button>
98
+ </a>
99
+ </>
100
+ );
101
+ }
102
+
103
+ function FormsList({ forms, isValidating, t }) {
104
+ const isTablet = useLayoutType() === "tablet";
105
+ const [filter, setFilter] = useState("");
106
+
107
+ const filteredRows = useMemo(() => {
108
+ if (!filter) {
109
+ return forms;
110
+ }
111
+
112
+ if (filter === "Published") {
113
+ return forms.filter((form) => form.published);
114
+ }
115
+
116
+ if (filter === "Unpublished") {
117
+ return forms.filter((form) => !form.published);
118
+ }
119
+
120
+ return forms;
121
+ }, [filter, forms]);
122
+
123
+ const tableHeaders = [
124
+ {
125
+ header: t("name", "Name"),
126
+ key: "name",
127
+ },
128
+ {
129
+ header: t("version", "Version"),
130
+ key: "version",
131
+ },
132
+ {
133
+ header: t("published", "Published"),
134
+ key: "published",
135
+ },
136
+ {
137
+ header: t("retired", "Retired"),
138
+ key: "retired",
139
+ },
140
+ {
141
+ header: t("schemaActions", "Schema actions"),
142
+ key: "actions",
143
+ },
144
+ ];
145
+
146
+ const tableRows = useMemo(
147
+ () =>
148
+ (filteredRows.length ? filteredRows : forms)?.map((form) => ({
149
+ ...form,
150
+ id: form.uuid,
151
+ published: <CustomTag condition={form.published} />,
152
+ retired: <CustomTag condition={form.retired} />,
153
+ actions: <ActionButtons form={form} />,
154
+ })),
155
+ [filteredRows, forms]
156
+ );
157
+
158
+ const handleStatusChange = ({ selectedItem }) => {
159
+ setFilter(selectedItem);
160
+ };
161
+
162
+ const handleFilter = ({
163
+ rowIds,
164
+ headers,
165
+ cellsById,
166
+ inputValue,
167
+ getCellId,
168
+ }: FilterProps): Array<string> => {
169
+ return rowIds.filter((rowId) =>
170
+ headers.some(({ key }) => {
171
+ const cellId = getCellId(rowId, key);
172
+ const filterableValue = cellsById[cellId].value;
173
+ const filterTerm = inputValue.toLowerCase();
174
+
175
+ return ("" + filterableValue).toLowerCase().includes(filterTerm);
176
+ })
177
+ );
178
+ };
179
+
180
+ return (
181
+ <>
182
+ <div className={styles.flexContainer}>
183
+ <div className={styles.filterContainer}>
184
+ <Dropdown
185
+ id="publishStatusFilter"
186
+ initialSelectedItem={"All"}
187
+ label=""
188
+ titleText={
189
+ t("filterByPublishedStatus", "Filter by publish status") + ":"
190
+ }
191
+ type="inline"
192
+ items={["All", "Published", "Unpublished"]}
193
+ onChange={handleStatusChange}
194
+ />
195
+ </div>
196
+ <div className={styles.backgroundDataFetchingIndicator}>
197
+ <span>{isValidating ? <InlineLoading /> : null}</span>
198
+ </div>
199
+ </div>
200
+ <DataTable
201
+ filterRows={handleFilter}
202
+ rows={tableRows}
203
+ headers={tableHeaders}
204
+ size={isTablet ? "sm" : "lg"}
205
+ useZebraStyles
206
+ >
207
+ {({
208
+ rows,
209
+ headers,
210
+ getTableProps,
211
+ getHeaderProps,
212
+ getRowProps,
213
+ onInputChange,
214
+ }) => (
215
+ <>
216
+ <TableContainer className={styles.tableContainer}>
217
+ <div className={styles.toolbarWrapper}>
218
+ <TableToolbar className={styles.tableToolbar}>
219
+ <TableToolbarContent>
220
+ <TableToolbarSearch
221
+ onChange={onInputChange}
222
+ placeholder={t("searchThisList", "Search this list")}
223
+ />
224
+ <Button
225
+ kind="primary"
226
+ iconDescription={t("createNewForm", "Create a new form")}
227
+ renderIcon={(props) => <Add size={16} {...props} />}
228
+ onClick={() =>
229
+ navigate({
230
+ to: `${window.spaBase}/form-builder/new`,
231
+ })
232
+ }
233
+ >
234
+ {t("createNewForm", "Create a new form")}
235
+ </Button>
236
+ </TableToolbarContent>
237
+ </TableToolbar>
238
+ </div>
239
+ <Table {...getTableProps()} className={styles.table}>
240
+ <TableHead>
241
+ <TableRow>
242
+ {headers.map((header) => (
243
+ <TableHeader {...getHeaderProps({ header })}>
244
+ {header.header}
245
+ </TableHeader>
246
+ ))}
247
+ </TableRow>
248
+ </TableHead>
249
+ <TableBody>
250
+ {rows.map((row) => (
251
+ <TableRow {...getRowProps({ row })}>
252
+ {row.cells.map((cell) => (
253
+ <TableCell key={cell.id}>{cell.value}</TableCell>
254
+ ))}
255
+ </TableRow>
256
+ ))}
257
+ </TableBody>
258
+ </Table>
259
+ </TableContainer>
260
+ {rows.length === 0 ? (
261
+ <div className={styles.tileContainer}>
262
+ <Tile className={styles.tile}>
263
+ <div className={styles.tileContent}>
264
+ <p className={styles.content}>
265
+ {t(
266
+ "noMatchingFormsToDisplay",
267
+ "No matching forms to display"
268
+ )}
269
+ </p>
270
+ <p className={styles.helper}>
271
+ {t("checkFilters", "Check the filters above")}
272
+ </p>
273
+ </div>
274
+ </Tile>
275
+ </div>
276
+ ) : null}
277
+ </>
278
+ )}
279
+ </DataTable>
280
+ </>
281
+ );
282
+ }
283
+
284
+ const Dashboard: React.FC = () => {
285
+ const { t } = useTranslation();
286
+ const { error, forms, isLoading, isValidating } = useForms();
287
+
288
+ return (
289
+ <div className={styles.container}>
290
+ <h3 className={styles.heading}>{t("formBuilder", "Form Builder")}</h3>
291
+ {(() => {
292
+ if (error) {
293
+ return <ErrorState error={error} />;
294
+ }
295
+
296
+ if (isLoading) {
297
+ return <DataTableSkeleton role="progressbar" />;
298
+ }
299
+
300
+ if (forms.length === 0) {
301
+ return <EmptyState />;
302
+ }
303
+
304
+ return <FormsList forms={forms} isValidating={isValidating} t={t} />;
305
+ })()}
306
+ </div>
307
+ );
308
+ };
309
+
310
+ export default Dashboard;
@@ -0,0 +1,112 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .container {
6
+ padding: 2rem;
7
+ background-color: $ui-01;
8
+ }
9
+
10
+ .backgroundDataFetchingIndicator {
11
+ flex: 1;
12
+ }
13
+
14
+ .flexContainer {
15
+ display: flex;
16
+ justify-content: space-between;
17
+ }
18
+
19
+ .toolbarWrapper {
20
+ position: relative;
21
+ display: flex;
22
+ height: spacing.$spacing-09;
23
+ justify-content: flex-end;
24
+ }
25
+
26
+ .tableToolbar {
27
+ width: 20%;
28
+ min-width: 12.5rem;
29
+ }
30
+
31
+ .table tr:last-of-type {
32
+ td {
33
+ border-bottom: none;
34
+ }
35
+ }
36
+
37
+ .heading {
38
+ margin-bottom: 1.5rem;
39
+ }
40
+
41
+ .toolbar {
42
+ position: relative;
43
+ display: flex;
44
+ width: 100%;
45
+ min-height: 3rem;
46
+ margin-bottom: 0.5rem;
47
+ }
48
+
49
+ .emptyContainer {
50
+ justify-content: center;
51
+ align-items: center;
52
+ width: 100%;
53
+ padding: 1rem;
54
+ }
55
+
56
+ .tableContainer {
57
+ padding: 0;
58
+
59
+ :global(.cds--data-table-content) {
60
+ border: 1px solid $ui-03;
61
+ border-bottom: none;
62
+ overflow: visible;
63
+ }
64
+
65
+ :global(.cds--table-toolbar) {
66
+ position: relative;
67
+ height: 2rem;
68
+ min-height: 0rem;
69
+ overflow: visible;
70
+ top: 0;
71
+ }
72
+
73
+ &:global(.cds--data-table-container) {
74
+ padding-top: 0rem;
75
+ }
76
+ }
77
+
78
+ .filterContainer {
79
+ flex: 1;
80
+
81
+ :global(.cds--dropdown__wrapper--inline) {
82
+ gap: 0;
83
+ }
84
+
85
+ :global(.cds--list-box__menu-icon) {
86
+ height: 1rem;
87
+ }
88
+ }
89
+
90
+ .content {
91
+ @include type.type-style('heading-compact-02');
92
+ color: $text-02;
93
+ margin-bottom: 0.5rem;
94
+ }
95
+
96
+ .tileContainer {
97
+ background-color: $ui-02;
98
+ border-top: 1px solid $ui-03;
99
+ padding: 5rem 0;
100
+ }
101
+
102
+ .tile {
103
+ margin: auto;
104
+ width: fit-content;
105
+ }
106
+
107
+ .tileContent {
108
+ display: flex;
109
+ flex-direction: column;
110
+ align-items: center;
111
+ }
112
+
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+
3
+ export const EmptyDataIllustration = ({ width = "64", height = "64" }) => {
4
+ return (
5
+ <svg width={width} height={height} viewBox="0 0 64 64">
6
+ <title>Empty data illustration</title>
7
+ <g fill="none" fillRule="evenodd">
8
+ <path
9
+ d="M38.133 13.186H21.947c-.768.001-1.39.623-1.39 1.391V50.55l-.186.057-3.97 1.216a.743.743 0 01-.927-.493L3.664 12.751a.742.742 0 01.492-.926l6.118-1.874 17.738-5.43 6.119-1.873a.741.741 0 01.926.492L38.076 13l.057.186z"
10
+ fill="#F4F4F4"
11
+ />
12
+ <path
13
+ d="M41.664 13L38.026 1.117A1.576 1.576 0 0036.056.07l-8.601 2.633-17.737 5.43-8.603 2.634a1.578 1.578 0 00-1.046 1.97l12.436 40.616a1.58 1.58 0 001.969 1.046l5.897-1.805.185-.057v-.194l-.185.057-5.952 1.822a1.393 1.393 0 01-1.737-.923L.247 12.682a1.39 1.39 0 01.923-1.738L9.772 8.31 27.51 2.881 36.112.247a1.393 1.393 0 011.737.923L41.47 13l.057.186h.193l-.057-.185z"
14
+ fill="#8D8D8D"
15
+ />
16
+ <path
17
+ d="M11.378 11.855a.836.836 0 01-.798-.59L9.385 7.361a.835.835 0 01.554-1.042l16.318-4.996a.836.836 0 011.042.554l1.195 3.902a.836.836 0 01-.554 1.043l-16.318 4.995a.831.831 0 01-.244.037z"
18
+ fill="#C6C6C6"
19
+ />
20
+ <circle fill="#C6C6C6" cx={17.636} cy={2.314} r={1.855} />
21
+ <circle
22
+ fill="#FFF"
23
+ fillRule="nonzero"
24
+ cx={17.636}
25
+ cy={2.314}
26
+ r={1.175}
27
+ />
28
+ <path
29
+ d="M55.893 53.995H24.544a.79.79 0 01-.788-.789V15.644a.79.79 0 01.788-.788h31.349a.79.79 0 01.788.788v37.562a.79.79 0 01-.788.789z"
30
+ fill="#F4F4F4"
31
+ />
32
+ <path
33
+ d="M41.47 13H21.948a1.579 1.579 0 00-1.576 1.577V52.4l.185-.057V14.577c.001-.768.623-1.39 1.391-1.39h19.581L41.471 13zm17.02 0H21.947a1.579 1.579 0 00-1.576 1.577v42.478c0 .87.706 1.576 1.576 1.577H58.49a1.579 1.579 0 001.576-1.577V14.577a1.579 1.579 0 00-1.576-1.576zm1.39 44.055c0 .768-.622 1.39-1.39 1.392H21.947c-.768-.001-1.39-.624-1.39-1.392V14.577c0-.768.622-1.39 1.39-1.39H58.49c.768 0 1.39.622 1.39 1.39v42.478z"
34
+ fill="#8D8D8D"
35
+ />
36
+ <path
37
+ d="M48.751 17.082H31.686a.836.836 0 01-.835-.835v-4.081c0-.46.374-.834.835-.835H48.75c.461 0 .834.374.835.835v4.08c0 .462-.374.835-.835.836z"
38
+ fill="#C6C6C6"
39
+ />
40
+ <circle fill="#C6C6C6" cx={40.218} cy={9.755} r={1.855} />
41
+ <circle
42
+ fill="#FFF"
43
+ fillRule="nonzero"
44
+ cx={40.218}
45
+ cy={9.755}
46
+ r={1.13}
47
+ />
48
+ </g>
49
+ </svg>
50
+ );
51
+ };
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { Layer, Link, Tile } from "@carbon/react";
3
+ import { useTranslation } from "react-i18next";
4
+ import { navigate, useLayoutType } from "@openmrs/esm-framework";
5
+
6
+ import styles from "./empty-state.scss";
7
+ import { EmptyDataIllustration } from "./empty-data-illustration.component";
8
+
9
+ function EmptyState() {
10
+ const { t } = useTranslation();
11
+ const isTablet = useLayoutType() === "tablet";
12
+
13
+ return (
14
+ <Layer>
15
+ <Tile className={styles.tile}>
16
+ <div
17
+ className={isTablet ? styles.tabletHeading : styles.desktopHeading}
18
+ >
19
+ <h4>{t("forms", "Forms")}</h4>
20
+ </div>
21
+ <EmptyDataIllustration />
22
+ <p className={styles.content}>
23
+ {t("noFormsToDisplay", "There are no forms to display.")}
24
+ </p>
25
+ <p className={styles.action}>
26
+ <Link
27
+ onClick={() =>
28
+ navigate({
29
+ to: `${window.spaBase}/form-builder/new`,
30
+ })
31
+ }
32
+ >
33
+ {t("createNewForm", "Create a new form")}
34
+ </Link>
35
+ </p>
36
+ </Tile>
37
+ </Layer>
38
+ );
39
+ }
40
+
41
+ export default EmptyState;
@@ -0,0 +1,55 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .action {
6
+ margin-bottom: spacing.$spacing-03;
7
+ }
8
+
9
+ .content {
10
+ @include type.type-style("heading-compact-01");
11
+ color: $text-02;
12
+ margin-top: spacing.$spacing-05;
13
+ margin-bottom: spacing.$spacing-03;
14
+ }
15
+
16
+ .desktopHeading {
17
+ h4 {
18
+ @include type.type-style('heading-compact-02');
19
+ color: $text-02;
20
+ }
21
+ }
22
+
23
+ .tabletHeading {
24
+ h4 {
25
+ @include type.type-style('heading-03');
26
+ color: $text-02;
27
+ }
28
+ }
29
+
30
+ .desktopHeading, .tabletHeading {
31
+ text-align: left;
32
+ text-transform: capitalize;
33
+ margin-bottom: spacing.$spacing-05;
34
+
35
+ h4:after {
36
+ content: "";
37
+ display: block;
38
+ width: 2rem;
39
+ padding-top: 0.188rem;
40
+ border-bottom: 0.375rem solid var(--brand-03);
41
+ }
42
+ }
43
+
44
+ .heading:after {
45
+ content: "";
46
+ display: block;
47
+ width: 2rem;
48
+ padding-top: 0.188rem;
49
+ border-bottom: 0.375rem solid var(--brand-03);
50
+ }
51
+
52
+ .tile {
53
+ text-align: center;
54
+ border: 1px solid $ui-03;
55
+ }