Skip to content

Commit

Permalink
feat: add Section Configure
Browse files Browse the repository at this point in the history
  • Loading branch information
CefBoud committed Dec 6, 2023
1 parent 8734daa commit 9c972de
Show file tree
Hide file tree
Showing 15 changed files with 518 additions and 1 deletion.
11 changes: 11 additions & 0 deletions src/course-outline/CourseOutline.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import SectionCard from './section-card/SectionCard';
import HighlightsModal from './highlights-modal/HighlightsModal';
import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder';
import PublishModal from './publish-modal/PublishModal';
import ConfigureModal from './configure-modal/ConfigureModal';
import DeleteModal from './delete-modal/DeleteModal';
import { useCourseOutline } from './hooks';
import messages from './messages';
Expand All @@ -51,11 +52,14 @@ const CourseOutline = ({ courseId }) => {
isDisabledReindexButton,
isHighlightsModalOpen,
isPublishModalOpen,
isConfigureModalOpen,
isDeleteModalOpen,
closeHighlightsModal,
closePublishModal,
closeConfigureModal,
closeDeleteModal,
openPublishModal,
openConfigureModal,
openDeleteModal,
headerNavigationsActions,
openEnableHighlightsModal,
Expand All @@ -65,6 +69,7 @@ const CourseOutline = ({ courseId }) => {
handleOpenHighlightsModal,
handleHighlightsFormSubmit,
handlePublishSectionSubmit,
handleConfigureSectionSubmit,
handleEditSectionSubmit,
handleDeleteSectionSubmit,
handleDuplicateSectionSubmit,
Expand Down Expand Up @@ -143,6 +148,7 @@ const CourseOutline = ({ courseId }) => {
savingStatus={savingStatus}
onOpenHighlightsModal={handleOpenHighlightsModal}
onOpenPublishModal={openPublishModal}
onOpenConfigureModal={openConfigureModal}
onOpenDeleteModal={openDeleteModal}
onEditSectionSubmit={handleEditSectionSubmit}
onDuplicateSubmit={handleDuplicateSectionSubmit}
Expand Down Expand Up @@ -188,6 +194,11 @@ const CourseOutline = ({ courseId }) => {
onClose={closePublishModal}
onPublishSubmit={handlePublishSectionSubmit}
/>
<ConfigureModal
isOpen={isConfigureModalOpen}
onClose={closeConfigureModal}
onConfigureSubmit={handleConfigureSectionSubmit}
/>
<DeleteModal
isOpen={isDeleteModalOpen}
close={closeDeleteModal}
Expand Down
1 change: 1 addition & 0 deletions src/course-outline/CourseOutline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
@import "./empty-placeholder/EmptyPlaceholder";
@import "./highlights-modal/HighlightsModal";
@import "./publish-modal/PublishModal";
@import "./configure-modal/ConfigureModal";
46 changes: 46 additions & 0 deletions src/course-outline/CourseOutline.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { executeThunk } from '../utils';
import CourseOutline from './CourseOutline';
import messages from './messages';
import headerMessages from './header-navigations/messages';
import cardHeaderMessages from './card-header/messages';

let axiosMock;
let store;
Expand Down Expand Up @@ -324,6 +325,51 @@ describe('<CourseOutline />', () => {
expect(firstSection.querySelector('.section-card-header__badge-status')).toHaveTextContent('Published not live');
});

it('check configure section when configure query is successful', async () => {
cleanup();
const { getAllByTestId, getByText, getByPlaceholderText } = render(<RootWrapper />);
const section = courseOutlineIndexMock.courseStructure.childInfo.children[0];
const newReleaseDate = '2025-08-10T10:00:00Z';
axiosMock
.onPost(getUpdateCourseSectionApiUrl(section.id), {
id: section.id,
data: null,
metadata: {
display_name: section.displayName,
start: newReleaseDate,
visible_to_staff_only: true,
},
})
.reply(200);

axiosMock
.onGet(getXBlockApiUrl(section.id))
.reply(200, {
...section,
start: newReleaseDate,
});

await executeThunk(fetchCourseSectionQuery(section.id), store.dispatch);

const firstSection = getAllByTestId('section-card')[0];

const sectionDropdownButton = firstSection.querySelector('#section-card-header__menu');
expect(sectionDropdownButton).toBeInTheDocument();
fireEvent.click(sectionDropdownButton);

const configureBtn = getByText(cardHeaderMessages.menuConfigure.defaultMessage);
fireEvent.click(configureBtn);

const datePicker = getByPlaceholderText('MM/DD/YYYY');

fireEvent.change(datePicker, { target: { value: '08/10/2025' } });
fireEvent.click(getByText('Save'));
fireEvent.click(sectionDropdownButton);
fireEvent.click(configureBtn);

expect(datePicker).toHaveValue('08/10/2025');
});

it('check update highlights when update highlights query is successfully', async () => {
const { getByRole } = render(<RootWrapper />);

Expand Down
4 changes: 3 additions & 1 deletion src/course-outline/card-header/CardHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const CardHeader = ({
hasChanges,
isExpanded,
onClickPublish,
onClickConfigure,
onClickMenuButton,
onClickEdit,
onExpand,
Expand Down Expand Up @@ -136,7 +137,7 @@ const CardHeader = ({
>
{intl.formatMessage(messages.menuPublish)}
</Dropdown.Item>
<Dropdown.Item>{intl.formatMessage(messages.menuConfigure)}</Dropdown.Item>
<Dropdown.Item onClick={onClickConfigure}>{intl.formatMessage(messages.menuConfigure)}</Dropdown.Item>
<Dropdown.Item onClick={onClickDuplicate}>{intl.formatMessage(messages.menuDuplicate)}</Dropdown.Item>
<Dropdown.Item onClick={onClickDelete}>{intl.formatMessage(messages.menuDelete)}</Dropdown.Item>
</Dropdown.Menu>
Expand All @@ -153,6 +154,7 @@ CardHeader.propTypes = {
isExpanded: PropTypes.bool.isRequired,
onExpand: PropTypes.func.isRequired,
onClickPublish: PropTypes.func.isRequired,
onClickConfigure: PropTypes.func.isRequired,
onClickMenuButton: PropTypes.func.isRequired,
onClickEdit: PropTypes.func.isRequired,
isFormOpen: PropTypes.bool.isRequired,
Expand Down
43 changes: 43 additions & 0 deletions src/course-outline/configure-modal/BasicTab.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Stack } from '@edx/paragon';
import { FormattedMessage, injectIntl, useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { DatepickerControl, DATEPICKER_TYPES } from '../../generic/datepicker-control';

const BasicTab = ({ releaseDate, setReleaseDate }) => {
const intl = useIntl();
const onChange = (value) => {
setReleaseDate(value);
};

return (
<>
<h3 className="mt-3"><FormattedMessage {...messages.releaseDateAndTime} /></h3>
<hr />
<Stack direction="horizontal" gap={5}>
<DatepickerControl
type={DATEPICKER_TYPES.date}
value={releaseDate}
label={intl.formatMessage(messages.releaseDate)}
controlName="state-date"
onChange={(date) => onChange(date)}
/>
<DatepickerControl
type={DATEPICKER_TYPES.time}
value={releaseDate}
label={intl.formatMessage(messages.releaseTimeUTC)}
controlName="start-time"
onChange={(date) => onChange(date)}
/>
</Stack>
</>
);
};

BasicTab.propTypes = {
releaseDate: PropTypes.string.isRequired,
setReleaseDate: PropTypes.func.isRequired,
};

export default injectIntl(BasicTab);
95 changes: 95 additions & 0 deletions src/course-outline/configure-modal/ConfigureModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/* eslint-disable import/named */
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
ModalDialog,
Button,
ActionRow,
Tab,
Tabs,
} from '@edx/paragon';
import { useSelector } from 'react-redux';

import { VisibilityTypes } from '../../data/constants';
import { getCurrentSection } from '../data/selectors';
import messages from './messages';
import BasicTab from './BasicTab';
import VisibilityTab from './VisibilityTab';

const ConfigureModal = ({
isOpen,
onClose,
onConfigureSubmit,
}) => {
const intl = useIntl();
const { displayName, start: sectionStartDate, visibilityState } = useSelector(getCurrentSection);
const [releaseDate, setReleaseDate] = useState(sectionStartDate);
const [isVisibleToStaffOnly, setIsVisibleToStaffOnly] = useState(visibilityState === VisibilityTypes.STAFF_ONLY);
const [saveButtonDisabled, setSaveButtonDisabled] = useState(true);

useEffect(() => {
setReleaseDate(sectionStartDate);
}, [sectionStartDate]);

useEffect(() => {
setIsVisibleToStaffOnly(visibilityState === VisibilityTypes.STAFF_ONLY);
}, [visibilityState]);

useEffect(() => {
const visibilityUnchanged = isVisibleToStaffOnly === (visibilityState === VisibilityTypes.STAFF_ONLY);
setSaveButtonDisabled(visibilityUnchanged && releaseDate === sectionStartDate);
}, [releaseDate, isVisibleToStaffOnly]);

const handleSave = () => {
onConfigureSubmit(isVisibleToStaffOnly, releaseDate);
};

return (
<ModalDialog
className="configure-modal"
isOpen={isOpen}
onClose={onClose}
hasCloseButton
isFullscreenOnMobile
>
<ModalDialog.Header className="configure-modal__header">
<ModalDialog.Title>
{intl.formatMessage(messages.title, { title: displayName })}
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body className="configure-modal__body">
<Tabs>
<Tab eventKey="basic" title={intl.formatMessage(messages.basicTabTitle)}>
<BasicTab releaseDate={releaseDate} setReleaseDate={setReleaseDate} />
</Tab>
<Tab eventKey="visibility" title={intl.formatMessage(messages.visibilityTabTitle)}>
<VisibilityTab
isVisibleToStaffOnly={isVisibleToStaffOnly}
setIsVisibleToStaffOnly={setIsVisibleToStaffOnly}
showWarning={visibilityState === VisibilityTypes.STAFF_ONLY}
/>
</Tab>
</Tabs>
</ModalDialog.Body>
<ModalDialog.Footer className="pt-1">
<ActionRow>
<ModalDialog.CloseButton variant="tertiary">
{intl.formatMessage(messages.cancelButton)}
</ModalDialog.CloseButton>
<Button onClick={handleSave} disabled={saveButtonDisabled}>
{intl.formatMessage(messages.saveButton)}
</Button>
</ActionRow>
</ModalDialog.Footer>
</ModalDialog>
);
};

ConfigureModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onConfigureSubmit: PropTypes.func.isRequired,
};

export default ConfigureModal;
12 changes: 12 additions & 0 deletions src/course-outline/configure-modal/ConfigureModal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.configure-modal {
max-width: 33.6875rem;
overflow: visible;

.configure-modal__header {
padding-top: 1.5rem;
}

.configure-modal__body {
overflow: visible;
}
}
Loading

0 comments on commit 9c972de

Please sign in to comment.