@openmrs/esm-form-builder-app 2.2.2-pre.645 → 2.2.2-pre.654

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.
@@ -3,7 +3,7 @@
3
3
  @use "@carbon/type";
4
4
 
5
5
  .container {
6
- padding: 2rem;
6
+ padding: 1rem;
7
7
  background-color: colors.$gray-10;
8
8
  }
9
9
 
@@ -49,6 +49,9 @@
49
49
  }
50
50
  }
51
51
 
52
+ .link {
53
+ text-decoration: none;
54
+ }
52
55
 
53
56
  .table {
54
57
  tr {
@@ -65,10 +68,6 @@
65
68
  }
66
69
  }
67
70
 
68
- .heading {
69
- margin-bottom: 1.5rem;
70
- }
71
-
72
71
  .toolbar {
73
72
  position: relative;
74
73
  display: flex;
@@ -105,10 +104,6 @@
105
104
  padding-top: 0rem;
106
105
  }
107
106
 
108
- // FIXME: This override should be moved to esm-styleguide
109
- :global(.cds--btn--icon-only) {
110
- padding-block-start: 0.5rem;
111
- }
112
107
  }
113
108
 
114
109
  .filterContainer {
@@ -123,6 +118,10 @@
123
118
  }
124
119
  }
125
120
 
121
+ .filterDropdown {
122
+ margin-left: layout.$spacing-03;
123
+ }
124
+
126
125
  .content {
127
126
  @include type.type-style('heading-compact-02');
128
127
  color: colors.$gray-70;
@@ -147,7 +146,7 @@
147
146
  }
148
147
 
149
148
  .warningMessage {
150
- margin: 1.5rem 0;
149
+ margin: 1rem 0;
151
150
  }
152
151
 
153
152
  .spinner {
@@ -8,6 +8,7 @@ import Dashboard from './dashboard.component';
8
8
 
9
9
  const mockedOpenmrsFetch = openmrsFetch as jest.Mock;
10
10
  const mockedDeleteForm = deleteForm as jest.Mock;
11
+ global.window.URL.createObjectURL = jest.fn();
11
12
 
12
13
  jest.mock('../../forms.resource', () => ({
13
14
  deleteForm: jest.fn(),
@@ -58,7 +59,7 @@ describe('Dashboard', () => {
58
59
 
59
60
  await waitForLoadingToFinish();
60
61
 
61
- expect(screen.getByRole('heading', { name: /form builder/i })).toBeInTheDocument();
62
+ expect(screen.getByText(/form builder/i)).toBeInTheDocument();
62
63
  expect(screen.getByRole('heading', { name: /forms/i })).toBeInTheDocument();
63
64
  expect(screen.getByTitle(/empty data illustration/i)).toBeInTheDocument();
64
65
  expect(screen.getByText(/there are no forms to display/i)).toBeInTheDocument();
@@ -141,14 +142,14 @@ describe('Dashboard', () => {
141
142
 
142
143
  await waitForLoadingToFinish();
143
144
 
144
- expect(screen.getByRole('heading', { name: /form builder/i })).toBeInTheDocument();
145
+ expect(screen.getByText(/form builder/i)).toBeInTheDocument();
145
146
  expect(screen.getByRole('combobox', { name: /filter by/i })).toBeInTheDocument();
146
147
  expect(screen.getByRole('button', { name: /create a new form/i })).toBeInTheDocument();
147
148
  expect(screen.getByRole('button', { name: /edit schema/i })).toBeInTheDocument();
148
149
  expect(screen.getByRole('button', { name: /download schema/i })).toBeInTheDocument();
149
150
  expect(screen.getByRole('searchbox', { name: /filter table/i })).toBeInTheDocument();
150
151
  expect(screen.queryByRole('table')).toBeInTheDocument();
151
- expect(screen.getByText(/Test Form 1/i)).toBeInTheDocument();
152
+ expect(screen.getByText(formsResponse[0].name)).toBeInTheDocument();
152
153
  });
153
154
 
154
155
  it('clicking on "create a new form" button navigates to the "create form" page', async () => {
@@ -181,6 +182,27 @@ describe('Dashboard', () => {
181
182
  });
182
183
  });
183
184
 
185
+ it("clicking the form name navigates to the form's edit page", async () => {
186
+ mockedOpenmrsFetch.mockReturnValueOnce({
187
+ data: {
188
+ results: formsResponse,
189
+ },
190
+ });
191
+
192
+ mockUsePagination.mockImplementation(() => ({
193
+ currentPage: 1,
194
+ goTo: () => {},
195
+ results: formsResponse,
196
+ }));
197
+
198
+ renderDashboard();
199
+
200
+ await waitForLoadingToFinish();
201
+
202
+ const link = screen.getByRole('link', { name: formsResponse[0].name });
203
+ expect(link).toBeInTheDocument();
204
+ });
205
+
184
206
  it('clicking on "edit schema" button navigates to the "edit schema" page', async () => {
185
207
  const user = userEvent.setup();
186
208
 
@@ -17,10 +17,9 @@ import {
17
17
  TabPanels,
18
18
  TabPanel,
19
19
  } from '@carbon/react';
20
- import { Download } from '@carbon/react/icons';
20
+ import { ArrowLeft, Download } from '@carbon/react/icons';
21
21
  import { useParams } from 'react-router-dom';
22
22
  import { useTranslation } from 'react-i18next';
23
- import { ExtensionSlot } from '@openmrs/esm-framework';
24
23
  import type { OHRIFormSchema } from '@openmrs/openmrs-form-engine-lib';
25
24
  import type { Schema } from '../../types';
26
25
  import { useClobdata } from '../../hooks/useClobdata';
@@ -28,9 +27,11 @@ import { useForm } from '../../hooks/useForm';
28
27
  import ActionButtons from '../action-buttons/action-buttons.component';
29
28
  import AuditDetails from '../audit-details/audit-details.component';
30
29
  import FormRenderer from '../form-renderer/form-renderer.component';
30
+ import Header from '../header/header.component';
31
31
  import InteractiveBuilder from '../interactive-builder/interactive-builder.component';
32
32
  import SchemaEditor from '../schema-editor/schema-editor.component';
33
33
  import styles from './form-editor.scss';
34
+ import { ConfigurableLink } from '@openmrs/esm-framework';
34
35
 
35
36
  interface ErrorProps {
36
37
  error: Error;
@@ -240,9 +241,8 @@ const FormEditor: React.FC = () => {
240
241
 
241
242
  return (
242
243
  <>
243
- <div className={styles.breadcrumbsContainer}>
244
- <ExtensionSlot name="breadcrumbs-slot" />
245
- </div>
244
+ <Header title={t('schemaEditor', 'Schema editor')} />
245
+ <BackButton />
246
246
  <div className={styles.container}>
247
247
  {showDraftSchemaModal && <DraftSchemaModal />}
248
248
  <Grid className={styles.grid}>
@@ -250,17 +250,21 @@ const FormEditor: React.FC = () => {
250
250
  <div className={styles.actionButtons}>
251
251
  {isLoadingFormOrSchema ? (
252
252
  <InlineLoading description={t('loadingSchema', 'Loading schema') + '...'} />
253
- ) : null}
253
+ ) : (
254
+ <h1 className={styles.formName}>{form?.name}</h1>
255
+ )}
254
256
 
255
- {isNewSchema && !schema ? (
256
- <Button kind="ghost" onClick={inputDummySchema}>
257
- {t('inputDummySchema', 'Input dummy schema')}
258
- </Button>
259
- ) : null}
257
+ <div>
258
+ {isNewSchema && !schema ? (
259
+ <Button kind="ghost" onClick={inputDummySchema}>
260
+ {t('inputDummySchema', 'Input dummy schema')}
261
+ </Button>
262
+ ) : null}
260
263
 
261
- <Button kind="ghost" onClick={renderSchemaChanges}>
262
- <span>{t('renderChanges', 'Render changes')}</span>
263
- </Button>
264
+ <Button kind="ghost" onClick={renderSchemaChanges}>
265
+ <span>{t('renderChanges', 'Render changes')}</span>
266
+ </Button>
267
+ </div>
264
268
  </div>
265
269
  <div>
266
270
  <div className={styles.heading}>
@@ -330,4 +334,22 @@ const FormEditor: React.FC = () => {
330
334
  );
331
335
  };
332
336
 
337
+ function BackButton() {
338
+ const { t } = useTranslation();
339
+
340
+ return (
341
+ <div className={styles.backButton}>
342
+ <ConfigurableLink to={window.getOpenmrsSpaBase() + 'form-builder'}>
343
+ <Button
344
+ kind="ghost"
345
+ renderIcon={(props) => <ArrowLeft size={24} {...props} />}
346
+ iconDescription="Return to dashboard"
347
+ >
348
+ <span>{t('backToDashboard', 'Back to dashboard')}</span>
349
+ </Button>
350
+ </ConfigurableLink>
351
+ </div>
352
+ );
353
+ }
354
+
333
355
  export default FormEditor;
@@ -8,12 +8,6 @@
8
8
  flex-direction: column;
9
9
  }
10
10
 
11
- .breadcrumbsContainer {
12
- nav {
13
- background-color: colors.$white-0;
14
- }
15
- }
16
-
17
11
  .grid {
18
12
  margin-left: 0;
19
13
  margin-right: 0;
@@ -41,7 +35,7 @@
41
35
  .actionButtons {
42
36
  display: flex;
43
37
  align-items: center;
44
- justify-content: flex-end;
38
+ justify-content: space-between;
45
39
  margin: 1rem 0;
46
40
 
47
41
  button {
@@ -49,6 +43,10 @@
49
43
  }
50
44
  }
51
45
 
46
+ .formName {
47
+ @include type.type-style('heading-03');
48
+ }
49
+
52
50
  .editorContainer {
53
51
  padding: 1rem;
54
52
  }
@@ -67,3 +65,31 @@
67
65
  width: 100%;
68
66
  padding: 0.75rem;
69
67
  }
68
+
69
+ .backButton {
70
+ margin-left: layout.$spacing-05;
71
+ padding: layout.$spacing-03 0;
72
+ max-width: fit-content;
73
+
74
+ a {
75
+ text-decoration: none;
76
+ }
77
+
78
+ button {
79
+ display: flex;
80
+ padding-left: 0 !important;
81
+
82
+ svg {
83
+ order: 1;
84
+ margin: 0 layout.$spacing-03;
85
+ }
86
+
87
+ span {
88
+ order: 2;
89
+ }
90
+ }
91
+ }
92
+
93
+ button {
94
+ padding-block-start: 0.5rem;
95
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Calendar, Location, UserFollow } from '@carbon/react/icons';
4
+ import { formatDate, useSession } from '@openmrs/esm-framework';
5
+ import Illustration from './illo.component';
6
+ import styles from './header.scss';
7
+
8
+ interface HeaderProps {
9
+ title: string;
10
+ }
11
+
12
+ const Header: React.FC<HeaderProps> = ({ title }) => {
13
+ const { t } = useTranslation();
14
+ const session = useSession();
15
+ const location = session?.sessionLocation?.display;
16
+
17
+ return (
18
+ <div className={styles.header}>
19
+ <div className={styles['left-justified-items']}>
20
+ <Illustration />
21
+ <div className={styles['page-labels']}>
22
+ <p>{t('formBuilder', 'Form builder')}</p>
23
+ <p className={styles['page-name']}>{title}</p>
24
+ </div>
25
+ </div>
26
+ <div className={styles['right-justified-items']}>
27
+ <div className={styles.userContainer}>
28
+ <p>{session?.user?.person?.display}</p>
29
+ <UserFollow size={16} className={styles.userIcon} />
30
+ </div>
31
+ <div className={styles['date-and-location']}>
32
+ <Location size={16} />
33
+ <span className={styles.value}>{location}</span>
34
+ <span className={styles.middot}>&middot;</span>
35
+ <Calendar size={16} />
36
+ <span className={styles.value}>{formatDate(new Date(), { mode: 'standard' })}</span>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ );
41
+ };
42
+
43
+ export default Header;
@@ -0,0 +1,67 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .header {
6
+ @include type.type-style('body-compact-02');
7
+ color: $text-02;
8
+ height: layout.$spacing-12;
9
+ background-color: $ui-02;
10
+ border-bottom: 1px solid $ui-03;
11
+ display: flex;
12
+ justify-content: space-between;
13
+ padding: layout.$spacing-05;
14
+ }
15
+
16
+ .left-justified-items {
17
+ display: flex;
18
+ flex-direction: row;
19
+ align-items: center;
20
+ cursor: pointer;
21
+ align-items: center;
22
+ }
23
+
24
+ .right-justified-items {
25
+ @include type.type-style('body-compact-02');
26
+ color: $text-02;
27
+ display: flex;
28
+ flex-direction: column;
29
+ justify-content: space-between;
30
+ }
31
+
32
+ .page-name {
33
+ @include type.type-style('heading-04');
34
+ }
35
+
36
+ .page-labels {
37
+ margin: layout.$spacing-03;
38
+
39
+ p:first-of-type {
40
+ margin-bottom: layout.$spacing-02;
41
+ }
42
+ }
43
+
44
+ .date-and-location {
45
+ display: flex;
46
+ justify-content: flex-end;
47
+ align-items: center;
48
+ }
49
+
50
+ .userContainer {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: flex-end;
54
+ gap: layout.$spacing-05;
55
+ }
56
+
57
+ .value {
58
+ margin-left: layout.$spacing-02;
59
+ }
60
+
61
+ .middot {
62
+ margin: 0 layout.$spacing-03;
63
+ }
64
+
65
+ .view {
66
+ @include type.type-style('label-01');
67
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+
3
+ const Illustration: React.FC = () => {
4
+ return (
5
+ <svg
6
+ height="64"
7
+ width="64"
8
+ viewBox="0 0 32 32"
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ xmlSpace="preserve"
11
+ fill-rule="evenodd"
12
+ clipRule="evenodd"
13
+ strokeLinejoin="round"
14
+ strokeMiterlimit="2"
15
+ >
16
+ <path
17
+ d="M27 31.36H8a.36.36 0 0 1-.36-.36v-1.64H6a.36.36 0 0 1-.36-.36v-1.64H5a.36.36 0 0 1-.36-.36V3A.36.36 0 0 1 5 2.64h4.64V2a.36.36 0 0 1 .36-.36h1.64V1A.36.36 0 0 1 12 .64h4a.36.36 0 0 1 .36.36v.64H18a.36.36 0 0 1 .36.36v.64H23a.36.36 0 0 1 .36.36v1.64H25a.36.36 0 0 1 .36.36v1.64H27a.36.36 0 0 1 .36.36v24a.36.36 0 0 1-.36.36Z"
18
+ fill="#d2e5e5"
19
+ />
20
+ <path d="M8.36 30.64h18.28V7.36h-1.28V29a.36.36 0 0 1-.36.36H8.36v1.28Z" fill="#8abab8" />
21
+ <path
22
+ d="M5.36 26.64h17.28V3.36h-4.28V4a.36.36 0 0 1-.36.36h-8A.36.36 0 0 1 9.64 4v-.64H5.36v23.28Z"
23
+ fill="#8abab8"
24
+ />
25
+ <path fill="#fff" d="M7.5 12.64h13v.72h-13zM7.5 8.64h13v.72h-13zM7.5 20.64h13v.72h-13zM7.5 16.64h13v.72h-13z" />
26
+ <path
27
+ d="M10.36 3.64h7.28V2.36H16a.36.36 0 0 1-.36-.36v-.64h-3.28V2a.36.36 0 0 1-.36.36h-1.64v1.28ZM6.36 28.64h18.28V5.36h-1.28V27a.36.36 0 0 1-.36.36H6.36v1.28Z"
28
+ fill="#8abab8"
29
+ />
30
+ </svg>
31
+ );
32
+ };
33
+
34
+ export default Illustration;
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { defineConfigSchema, getAsyncLifecycle, registerBreadcrumbs } from '@openmrs/esm-framework';
1
+ import { defineConfigSchema, getAsyncLifecycle } from '@openmrs/esm-framework';
2
2
  import { configSchema } from './config-schema';
3
3
 
4
4
  const moduleName = '@openmrs/esm-form-builder-app';
@@ -19,22 +19,4 @@ export const systemAdministrationFormBuilderCardLink = getAsyncLifecycle(
19
19
 
20
20
  export function startupApp() {
21
21
  defineConfigSchema(moduleName, configSchema);
22
-
23
- registerBreadcrumbs([
24
- {
25
- path: `${window.spaBase}/form-builder`,
26
- title: 'Form Builder',
27
- parent: `${window.spaBase}/home`,
28
- },
29
- {
30
- path: `${window.spaBase}/form-builder/new`,
31
- title: 'Form Editor',
32
- parent: `${window.spaBase}/form-builder`,
33
- },
34
- {
35
- path: `${window.spaBase}/form-builder/edit/:uuid`,
36
- title: 'Form Editor',
37
- parent: `${window.spaBase}/form-builder`,
38
- },
39
- ]);
40
22
  }
@@ -1,3 +1,6 @@
1
1
  import '@testing-library/jest-dom';
2
2
 
3
3
  window.URL.createObjectURL = jest.fn();
4
+ window.openmrsBase = '/openmrs';
5
+ window.spaBase = '/spa';
6
+ window.getOpenmrsSpaBase = () => '/openmrs/spa/';