Skip to content

Commit 3c25f86

Browse files
(feat) O3-4322: Allow user to input/edit whether a section should be expanded (#399)
* Done with input left with editing * Creating a edit section modal * message change * message change * Rectifying default behaviour * Combining the edit and the create section modals * Additional changes * test changes * Test changes * . * Final modification * Final modification * . * Review based changes * Typo in translations --------- Co-authored-by: Nethmi Rodrigo <[email protected]>
1 parent 5531042 commit 3c25f86

File tree

8 files changed

+168
-91
lines changed

8 files changed

+168
-91
lines changed

e2e/pages/form-builder-page.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class FormBuilderPage {
4242
readonly pageCreatedMessage = () => this.page.getByText(/new page created/i);
4343
readonly addSectionButton = () => this.page.getByRole('button', { name: /add section/i });
4444
readonly sectionNameInput = () => this.page.getByRole('textbox', { name: /enter a section title/i });
45+
readonly isExpandedCheckbox = () => this.page.getByTestId('keep-section-expanded-checkbox');
4546
readonly editSectionButton = () => this.page.getByRole('button', { name: /edit section/i });
4647
readonly editSectionNameInput = () => this.page.locator('#sectionNameInput');
4748
readonly deleteSectionButton = () => this.page.getByRole('button', { name: /delete section/i });

e2e/specs/edit-form-with-interactive-builder.spec.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,29 @@ test('Edit a form using the interactive builder', async ({ page, context }) => {
144144
});
145145

146146
await test.step('And then I fill in the updated section name', async () => {
147-
await formBuilderPage.editSectionNameInput().fill('An edited section');
147+
await formBuilderPage.sectionNameInput().fill('An edited section');
148148
updatedForm.pages[0].sections[0].label = 'An edited section';
149149
});
150150

151+
await test.step('And then I check the expand section checkbox', async () => {
152+
await page.evaluate(() => {
153+
const checkbox = document.querySelector(
154+
'input[data-testid="keep-section-expanded-checkbox"]',
155+
) as HTMLInputElement;
156+
if (checkbox) {
157+
checkbox.click();
158+
}
159+
});
160+
updatedForm.pages[0].sections[0].isExpanded = 'false';
161+
});
162+
151163
await test.step('Then I click the `Save` button', async () => {
164+
await expect(formBuilderPage.saveButton()).toBeEnabled();
152165
await formBuilderPage.saveButton().click();
153166
});
154167

155168
await test.step('Then I should get a success message and the section name should be renamed', async () => {
156-
await expect(formBuilderPage.page.getByText(/section renamed/i)).toBeVisible();
169+
await expect(formBuilderPage.page.getByText(/section edited/i)).toBeVisible();
157170
await expect(formBuilderPage.page.getByRole('heading', { level: 1, name: /an edited section/i })).toBeVisible();
158171
const formTextContent = await formBuilderPage.schemaEditorContent().textContent();
159172
expect(JSON.parse(formTextContent)).toEqual(updatedForm);

src/components/interactive-builder/interactive-builder.component.tsx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { DndContext, KeyboardSensor, MouseSensor, closestCorners, useSensor, useSensors } from '@dnd-kit/core';
44
import { Accordion, AccordionItem, Button, IconButton, InlineLoading } from '@carbon/react';
5-
import { Add, TrashCan } from '@carbon/react/icons';
5+
import { Add, TrashCan, Edit } from '@carbon/react/icons';
66
import { useParams } from 'react-router-dom';
77
import { showModal, showSnackbar } from '@openmrs/esm-framework';
88
import DraggableQuestion from './draggable/draggable-question.component';
@@ -102,6 +102,21 @@ const InteractiveBuilder: React.FC<InteractiveBuilderProps> = ({
102102
[schema, onSchemaChange],
103103
);
104104

105+
const launchEditSectionModal = useCallback(
106+
(pageIndex: number, sectionIndex: number) => {
107+
const modalType = 'edit';
108+
const dispose = showModal('new-section-modal', {
109+
closeModal: () => dispose(),
110+
pageIndex,
111+
sectionIndex,
112+
schema,
113+
onSchemaChange,
114+
modalType,
115+
});
116+
},
117+
[onSchemaChange, schema],
118+
);
119+
105120
const launchDeleteSectionModal = useCallback(
106121
(pageIndex: number, sectionIndex: number) => {
107122
const dispose = showModal('delete-section-modal', {
@@ -416,12 +431,16 @@ const InteractiveBuilder: React.FC<InteractiveBuilderProps> = ({
416431
<>
417432
<div style={{ display: 'flex', alignItems: 'center' }}>
418433
<div className={styles.editorContainer}>
419-
<EditableValue
420-
elementType="section"
421-
id="sectionNameInput"
422-
value={section.label}
423-
onSave={(name) => renameSection(name, pageIndex, sectionIndex)}
424-
/>
434+
<h1 className={styles['sectionLabel']}>{section.label}</h1>
435+
<IconButton
436+
enterDelayMs={300}
437+
kind="ghost"
438+
label={t('editSection', 'Edit Section')}
439+
onClick={() => launchEditSectionModal(pageIndex, sectionIndex)}
440+
size="md"
441+
>
442+
<Edit />
443+
</IconButton>
425444
</div>
426445
<IconButton
427446
enterDelayMs={300}

src/components/interactive-builder/interactive-builder.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
margin: layout.$spacing-07 0;
2929
}
3030

31+
.sectionLabel {
32+
@include type.type-style('heading-02');
33+
}
34+
3135
.explainer {
3236
margin: layout.$spacing-05 layout.$spacing-03;
3337

src/components/interactive-builder/modals/new-section/section.modal.tsx

Lines changed: 0 additions & 81 deletions
This file was deleted.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React, { useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { Button, Form, FormGroup, ModalBody, ModalFooter, ModalHeader, TextInput, Checkbox } from '@carbon/react';
4+
import { showSnackbar } from '@openmrs/esm-framework';
5+
import type { Schema } from '@types';
6+
import styles from '../modals.scss';
7+
8+
interface SectionModalProps {
9+
closeModal: () => void;
10+
schema: Schema;
11+
onSchemaChange: (schema: Schema) => void;
12+
pageIndex: number;
13+
sectionIndex?: number;
14+
modalType?: 'edit';
15+
}
16+
17+
const SectionModal: React.FC<SectionModalProps> = ({
18+
closeModal,
19+
schema,
20+
onSchemaChange,
21+
pageIndex,
22+
sectionIndex,
23+
modalType,
24+
}) => {
25+
const { t } = useTranslation();
26+
const [sectionTitle, setSectionTitle] = useState<string>(
27+
modalType === 'edit' ? schema.pages[pageIndex].sections[sectionIndex].label : '',
28+
);
29+
const [isExpanded, setIsExpanded] = useState<string>(
30+
modalType === 'edit' ? schema.pages[pageIndex].sections[sectionIndex].isExpanded : 'true',
31+
);
32+
33+
const handleUpdatePageSections = () => {
34+
updateSections();
35+
closeModal();
36+
};
37+
38+
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>, { checked }: { checked: boolean }) => {
39+
checked === true ? setIsExpanded('true') : setIsExpanded('false');
40+
};
41+
42+
const updateSections = () => {
43+
try {
44+
if (modalType === 'edit') {
45+
schema.pages[pageIndex].sections[sectionIndex].label = sectionTitle;
46+
schema.pages[pageIndex].sections[sectionIndex].isExpanded = isExpanded;
47+
} else {
48+
schema.pages[pageIndex]?.sections?.push({
49+
label: sectionTitle,
50+
isExpanded: isExpanded,
51+
questions: [],
52+
});
53+
}
54+
onSchemaChange({ ...schema });
55+
setSectionTitle('');
56+
57+
showSnackbar({
58+
title: t('success', 'Success!'),
59+
kind: 'success',
60+
isLowContrast: true,
61+
subtitle:
62+
modalType === 'edit' ? t('sectionEdited', 'Section edited') : t('sectionCreated', 'New section created'),
63+
});
64+
} catch (error) {
65+
if (error instanceof Error) {
66+
showSnackbar({
67+
title:
68+
modalType === 'edit'
69+
? t('errorCreatingSection', 'Error creating section')
70+
: t('errorEditingSection', 'Error editing section'),
71+
kind: 'error',
72+
subtitle: error?.message,
73+
});
74+
}
75+
}
76+
};
77+
78+
return (
79+
<>
80+
<ModalHeader
81+
className={styles.modalHeader}
82+
title={modalType === 'edit' ? t('editSection', 'Edit section') : t('createNewSection', 'Create a new section')}
83+
closeModal={closeModal}
84+
/>
85+
<Form onSubmit={(event: React.SyntheticEvent) => event.preventDefault()}>
86+
<ModalBody>
87+
<FormGroup legendText={''}>
88+
<TextInput
89+
id="sectionTitle"
90+
labelText={t('enterSectionTitle', 'Enter a section title')}
91+
value={sectionTitle}
92+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setSectionTitle(event.target.value)}
93+
style={{ marginBottom: '16px' }}
94+
/>
95+
96+
<Checkbox
97+
id="isExpanded"
98+
checked={isExpanded === 'true' ? true : false}
99+
labelText={t('expandedSection', 'Keep section expanded')}
100+
onChange={handleCheckboxChange}
101+
data-testid="keep-section-expanded-checkbox"
102+
/>
103+
</FormGroup>
104+
</ModalBody>
105+
</Form>
106+
<ModalFooter>
107+
<Button onClick={closeModal} kind="secondary">
108+
{t('cancel', 'Cancel')}
109+
</Button>
110+
<Button disabled={!sectionTitle} onClick={handleUpdatePageSections}>
111+
<span>{t('save', 'Save')}</span>
112+
</Button>
113+
</ModalFooter>
114+
</>
115+
);
116+
};
117+
export default SectionModal;

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const deletePageModal = getAsyncLifecycle(
3333
);
3434

3535
export const newSectionModal = getAsyncLifecycle(
36-
() => import('./components/interactive-builder/modals/new-section/section.modal'),
36+
() => import('./components/interactive-builder/modals/section/section.modal'),
3737
options,
3838
);
3939

translations/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"editButton": "Edit {{elementType}}",
5555
"editQuestion": "Edit question",
5656
"editSchema": "Edit schema",
57+
"editSection": "Edit section",
5758
"encounterType": "Encounter Type",
5859
"enterPageTitle": "Enter a title for your new page",
5960
"enterSectionTitle": "Enter a section title",
@@ -67,6 +68,7 @@
6768
"errorDeletingQuestion": "Error deleting question",
6869
"errorDeletingSection": "Error deleting section",
6970
"errorDuplicatingQuestion": "Error duplicating question",
71+
"errorEditingSection": "Error editing section",
7072
"errorFetchingConceptName": "Couldn't resolve concept name",
7173
"errorFetchingConcepts": "Error fetching concepts",
7274
"errorFetchingPatientIdentifierTypes": "Error fetching patient identifier types",
@@ -81,6 +83,7 @@
8183
"errorSavingQuestion": "Error saving question",
8284
"errorUnpublishingForm": "Error unpublishing form",
8385
"errorUpdatingForm": "Error updating form",
86+
"expandedSection": "Keep section expanded",
8487
"expandSectionExplainer": "Below are the sections linked to this page. Expand each section to add questions to it.",
8588
"filterBy": "Filter by",
8689
"formBuilder": "Form Builder",
@@ -198,6 +201,7 @@
198201
"searchThisList": "Search this list",
199202
"sectionCreated": "New section created",
200203
"SectionDeleted": "Section deleted",
204+
"sectionEdited": "Section edited",
201205
"sectionExplainer": "A section will typically contain one or more questions. Click the button below to add a question to this section.",
202206
"sectionRenamed": "Section renamed",
203207
"selectAnswersToDisplay": "Select answers to display",

0 commit comments

Comments
 (0)