feat: implemented discussion restriction UI (#494)
* feat: implemented discussion restriction UI * refactor: fixed UI figma design issues * refactor: fixed 2nd review points * refactor: fixed review issues regarding confirmation popup * refactor: changed tab component to button group * perf: performance improvement changes * refactor: fixed memorization issues * refactor: fixed memo issues --------- Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com> Co-authored-by: SundasNoreen <sundas.noreen@arbisoft.com>
This commit is contained in:
@@ -79,4 +79,4 @@ CollapsableEditor.defaultProps = {
|
||||
},
|
||||
};
|
||||
|
||||
export default CollapsableEditor;
|
||||
export default React.memo(CollapsableEditor);
|
||||
|
||||
@@ -9,6 +9,10 @@ const ConfirmationPopup = ({
|
||||
confirmLabel,
|
||||
onCancel,
|
||||
cancelLabel,
|
||||
confirmVariant,
|
||||
confirmButtonClass,
|
||||
cancelButtonClass,
|
||||
sectionClasses,
|
||||
}) => (
|
||||
<Card className="rounded mb-3 px-1">
|
||||
<Card.Header
|
||||
@@ -17,12 +21,12 @@ const ConfirmationPopup = ({
|
||||
size="sm"
|
||||
/>
|
||||
<Card.Body>
|
||||
<Card.Section className="text-justify text-muted pt-2 pb-3">{bodyText}</Card.Section>
|
||||
<Card.Section className={`text-justify text-muted pt-2 pb-3 ${sectionClasses}`}>{bodyText}</Card.Section>
|
||||
<Card.Footer>
|
||||
<Button variant="tertiary" onClick={onCancel}>
|
||||
<Button variant="tertiary" className={cancelButtonClass} onClick={onCancel}>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button variant="outline-brand" className="ml-2" onClick={onConfirm}>
|
||||
<Button variant={confirmVariant} className={`ml-2 ${confirmButtonClass}`} onClick={onConfirm}>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
@@ -37,6 +41,16 @@ ConfirmationPopup.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
confirmLabel: PropTypes.string.isRequired,
|
||||
cancelLabel: PropTypes.string.isRequired,
|
||||
confirmButtonClass: PropTypes.string,
|
||||
cancelButtonClass: PropTypes.string,
|
||||
confirmVariant: PropTypes.string,
|
||||
sectionClasses: PropTypes.string,
|
||||
};
|
||||
ConfirmationPopup.defaultProps = {
|
||||
confirmVariant: 'outline-brand',
|
||||
confirmButtonClass: '',
|
||||
cancelButtonClass: '',
|
||||
sectionClasses: '',
|
||||
};
|
||||
|
||||
export default ConfirmationPopup;
|
||||
export default React.memo(ConfirmationPopup);
|
||||
|
||||
@@ -50,4 +50,4 @@ FieldFeedback.defaultProps = {
|
||||
errorMessage: '',
|
||||
};
|
||||
|
||||
export default FieldFeedback;
|
||||
export default React.memo(FieldFeedback);
|
||||
|
||||
@@ -12,7 +12,7 @@ import messages from '../../messages';
|
||||
import { checkFieldErrors } from '../../utils';
|
||||
import AnonymousPostingFields from '../shared/AnonymousPostingFields';
|
||||
import AppConfigFormDivider from '../shared/AppConfigFormDivider';
|
||||
import BlackoutDatesField from '../shared/BlackoutDatesField';
|
||||
import DiscussionRestriction from '../shared/DiscussionRestriction';
|
||||
import DiscussionTopics from '../shared/discussion-topics/DiscussionTopics';
|
||||
import DivisionByGroupFields from '../shared/DivisionByGroupFields';
|
||||
import ReportedContentEmailNotifications from '../shared/ReportedContentEmailNotifications';
|
||||
@@ -38,7 +38,7 @@ const OpenedXConfigForm = ({
|
||||
allowAnonymousPostsPeers: appConfigObj?.allowAnonymousPostsPeers || false,
|
||||
reportedContentEmailNotifications: appConfigObj?.reportedContentEmailNotifications || false,
|
||||
enableReportedContentEmailNotifications: Boolean(appConfigObj?.enableReportedContentEmailNotifications) || false,
|
||||
blackoutDates: appConfigObj?.blackoutDates || [],
|
||||
restrictedDates: appConfigObj?.restrictedDates || [],
|
||||
discussionTopics: discussionTopicsModel || [],
|
||||
divideByCohorts: appConfigObj?.divideByCohorts || false,
|
||||
divideCourseTopicsByCohorts: appConfigObj?.divideCourseTopicsByCohorts || false,
|
||||
@@ -53,27 +53,27 @@ const OpenedXConfigForm = ({
|
||||
};
|
||||
const validationSchema = Yup.object().shape({
|
||||
// eslint-disable-next-line react/forbid-prop-types
|
||||
blackoutDates: Yup.array(
|
||||
restrictedDates: Yup.array(
|
||||
Yup.object().shape({
|
||||
startDate: Yup.string()
|
||||
.checkFormat(intl.formatMessage(messages.blackoutStartDateInValidFormat), 'date')
|
||||
.required(intl.formatMessage(messages.blackoutStartDateRequired)),
|
||||
.checkFormat(intl.formatMessage(messages.restrictedStartDateInValidFormat), 'date')
|
||||
.required(intl.formatMessage(messages.restrictedStartDateRequired)),
|
||||
endDate: Yup.string()
|
||||
.checkFormat(intl.formatMessage(messages.blackoutEndDateInValidFormat), 'date')
|
||||
.required(intl.formatMessage(messages.blackoutEndDateRequired))
|
||||
.checkFormat(intl.formatMessage(messages.restrictedEndDateInValidFormat), 'date')
|
||||
.required(intl.formatMessage(messages.restrictedEndDateRequired))
|
||||
.when('startDate', {
|
||||
is: (startDate) => startDate,
|
||||
then: Yup.string().compare(intl.formatMessage(messages.blackoutEndDateInPast), 'date'),
|
||||
then: Yup.string().compare(intl.formatMessage(messages.restrictedEndDateInPast), 'date'),
|
||||
}),
|
||||
startTime: Yup.string().checkFormat(
|
||||
intl.formatMessage(messages.blackoutStartTimeInValidFormat),
|
||||
intl.formatMessage(messages.restrictedStartTimeInValidFormat),
|
||||
'time',
|
||||
),
|
||||
endTime: Yup.string()
|
||||
.checkFormat(intl.formatMessage(messages.blackoutEndTimeInValidFormat), 'time')
|
||||
.checkFormat(intl.formatMessage(messages.restrictedEndTimeInValidFormat), 'time')
|
||||
.when('startTime', {
|
||||
is: (startTime) => startTime,
|
||||
then: Yup.string().compare(intl.formatMessage(messages.blackoutEndTimeInPast), 'time'),
|
||||
then: Yup.string().compare(intl.formatMessage(messages.restrictedEndTimeInPast), 'time'),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
@@ -96,23 +96,23 @@ const OpenedXConfigForm = ({
|
||||
{({
|
||||
handleSubmit, handleChange, handleBlur, values, errors, touched,
|
||||
}) => {
|
||||
const { discussionTopics, blackoutDates } = values;
|
||||
const { discussionTopics, restrictedDates } = values;
|
||||
const discussionTopicErrors = discussionTopics.map((value, index) => checkFieldErrors(touched, errors, `discussionTopics.${index}`, 'name'));
|
||||
const blackoutDatesErrors = blackoutDates.map(
|
||||
(value, index) => checkFieldErrors(touched, errors, `blackoutDates.${index}`, 'startDate')
|
||||
|| checkFieldErrors(touched, errors, `blackoutDates.${index}`, 'endDate')
|
||||
|| checkFieldErrors(touched, errors, `blackoutDates.${index}`, 'startTime')
|
||||
|| checkFieldErrors(touched, errors, `blackoutDates.${index}`, 'endTime'),
|
||||
const restrictedDatesErrors = restrictedDates.map(
|
||||
(value, index) => checkFieldErrors(touched, errors, `restrictedDates.${index}`, 'startDate')
|
||||
|| checkFieldErrors(touched, errors, `restrictedDates.${index}`, 'endDate')
|
||||
|| checkFieldErrors(touched, errors, `restrictedDates.${index}`, 'startTime')
|
||||
|| checkFieldErrors(touched, errors, `restrictedDates.${index}`, 'endTime'),
|
||||
);
|
||||
|
||||
const contextValue = {
|
||||
validDiscussionTopics,
|
||||
setValidDiscussionTopics,
|
||||
discussionTopicErrors,
|
||||
blackoutDatesErrors,
|
||||
restrictedDatesErrors,
|
||||
isFormInvalid:
|
||||
discussionTopicErrors.some((error) => error)
|
||||
|| blackoutDatesErrors.some((error) => error),
|
||||
|| restrictedDatesErrors.some((error) => error),
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -139,7 +139,7 @@ const OpenedXConfigForm = ({
|
||||
<DivisionByGroupFields />
|
||||
<AppConfigFormDivider thick />
|
||||
<ReportedContentEmailNotifications />
|
||||
<BlackoutDatesField />
|
||||
<DiscussionRestriction />
|
||||
</Form>
|
||||
</Card>
|
||||
</OpenedXConfigFormProvider>
|
||||
|
||||
@@ -49,7 +49,7 @@ const defaultAppConfig = (divideDiscussionIds = []) => ({
|
||||
reportedContentEmailNotifications: false,
|
||||
enableReportedContentEmailNotifications: false,
|
||||
allowDivisionByUnit: false,
|
||||
blackoutDates: [],
|
||||
restrictedDates: [],
|
||||
cohortsEnabled: false,
|
||||
});
|
||||
describe('OpenedXConfigForm', () => {
|
||||
@@ -169,8 +169,8 @@ describe('OpenedXConfigForm', () => {
|
||||
expect(container.querySelector('#reportedContentEmailNotifications')).toBeInTheDocument();
|
||||
expect(container.querySelector('#reportedContentEmailNotifications')).not.toBeChecked();
|
||||
|
||||
// BlackoutDatesField
|
||||
expect(queryByText(container, messages.blackoutDatesLabel.defaultMessage)).toBeInTheDocument();
|
||||
// Discussion Restriction Field
|
||||
expect(queryByText(container, messages.discussionRestrictionLabel.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('folded sub-fields are in the DOM when parents are enabled', async () => {
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
import { Add } from '@edx/paragon/icons';
|
||||
|
||||
import { FieldArray, useFormikContext } from 'formik';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import messages from '../../messages';
|
||||
import BlackoutDatesItem from './blackout-dates/BlackoutDatesItem';
|
||||
import { checkStatus } from '../../utils';
|
||||
import { denormalizeBlackoutDate } from '../../../data/api';
|
||||
import { blackoutDatesStatus as STATUS } from '../../../data/constants';
|
||||
|
||||
const BlackoutDatesField = ({ intl }) => {
|
||||
const {
|
||||
values: appConfig,
|
||||
setFieldValue,
|
||||
errors,
|
||||
validateForm,
|
||||
} = useFormikContext();
|
||||
const { blackoutDates } = appConfig;
|
||||
|
||||
const handleOnClose = useCallback((index) => {
|
||||
const updatedBlackoutDates = [...blackoutDates];
|
||||
updatedBlackoutDates[index] = {
|
||||
...updatedBlackoutDates[index],
|
||||
status: checkStatus(denormalizeBlackoutDate(updatedBlackoutDates[index])),
|
||||
};
|
||||
setFieldValue('blackoutDates', updatedBlackoutDates);
|
||||
}, [blackoutDates]);
|
||||
|
||||
const newBlackoutDateItem = {
|
||||
id: uuid(),
|
||||
startDate: '',
|
||||
startTime: '',
|
||||
endDate: '',
|
||||
endTime: '',
|
||||
status: STATUS.UPCOMING,
|
||||
};
|
||||
|
||||
const onAddNewItem = async (push) => {
|
||||
await push(newBlackoutDateItem);
|
||||
validateForm();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<h5 className="text-gray-500 mt-4 mb-2">
|
||||
{intl.formatMessage(messages.blackoutDatesLabel)}
|
||||
</h5>
|
||||
<label className="text-primary-500 mb-1 h4">
|
||||
{intl.formatMessage(messages.blackoutDates)}
|
||||
</label>
|
||||
<div className="small mb-4 text-muted">
|
||||
{intl.formatMessage(messages.blackoutDatesHelp)}
|
||||
</div>
|
||||
<div>
|
||||
<FieldArray
|
||||
name="blackoutDates"
|
||||
render={({ push, remove }) => (
|
||||
<div>
|
||||
{blackoutDates.map((blackoutDate, index) => (
|
||||
<BlackoutDatesItem
|
||||
fieldNameCommonBase={`blackoutDates.${index}`}
|
||||
blackoutDate={blackoutDate}
|
||||
key={`date-${blackoutDate.id}`}
|
||||
id={blackoutDate.id}
|
||||
onDelete={() => remove(index)}
|
||||
onClose={() => handleOnClose(index)}
|
||||
hasError={Boolean(errors?.blackoutDates?.[index])}
|
||||
/>
|
||||
))}
|
||||
<div className="mb-4">
|
||||
<Button
|
||||
onClick={() => onAddNewItem(push)}
|
||||
variant="link"
|
||||
iconBefore={Add}
|
||||
className="text-primary-500 p-0"
|
||||
>
|
||||
{intl.formatMessage(messages.addBlackoutDatesButton)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
BlackoutDatesField.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BlackoutDatesField);
|
||||
@@ -0,0 +1,137 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button, ButtonGroup } from '@edx/paragon';
|
||||
import { Add } from '@edx/paragon/icons';
|
||||
|
||||
import { FieldArray, useFormikContext } from 'formik';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import ConfirmationPopup from '../../../../../generic/ConfirmationPopup';
|
||||
|
||||
import messages from '../../messages';
|
||||
import DiscussionRestrictionItem from './discussion-restrictions/DiscussionRestrictionItem';
|
||||
import { checkStatus } from '../../utils';
|
||||
import { denormalizeRestrictedDate } from '../../../data/api';
|
||||
import { restrictedDatesStatus as STATUS, discussionRestrictionOptions } from '../../../data/constants';
|
||||
import DiscussionRestrictionOption from './discussion-restrictions/DiscussionRestrictionOption';
|
||||
|
||||
const DiscussionRestriction = () => {
|
||||
const {
|
||||
values: appConfig,
|
||||
setFieldValue,
|
||||
errors,
|
||||
validateForm,
|
||||
} = useFormikContext();
|
||||
|
||||
const intl = useIntl();
|
||||
const { restrictedDates } = appConfig;
|
||||
const [selectedOption, setSelectedOption] = useState('');
|
||||
|
||||
const handleOnClose = useCallback((index) => {
|
||||
const updatedRestrictedDates = [...restrictedDates];
|
||||
updatedRestrictedDates[index] = {
|
||||
...updatedRestrictedDates[index],
|
||||
status: checkStatus(denormalizeRestrictedDate(updatedRestrictedDates[index])),
|
||||
};
|
||||
setFieldValue('restrictedDates', updatedRestrictedDates);
|
||||
}, [restrictedDates]);
|
||||
|
||||
const newRestrictedDateItem = {
|
||||
id: uuid(),
|
||||
startDate: '',
|
||||
startTime: '',
|
||||
endDate: '',
|
||||
endTime: '',
|
||||
status: STATUS.UPCOMING,
|
||||
};
|
||||
|
||||
const onAddNewItem = useCallback(async (push) => {
|
||||
await push(newRestrictedDateItem);
|
||||
validateForm();
|
||||
}, []);
|
||||
|
||||
const handleClick = useCallback((value) => {
|
||||
setSelectedOption(value);
|
||||
}, []);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
setSelectedOption('');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="discussion-restriction">
|
||||
<h5 className="text-gray-500 mt-4 mb-3 line-height-20">
|
||||
{intl.formatMessage(messages.discussionRestrictionLabel)}
|
||||
</h5>
|
||||
<ButtonGroup className="mb-3 w-100 d-flex flex-row height-36">
|
||||
{discussionRestrictionOptions.map((option) => (
|
||||
<DiscussionRestrictionOption
|
||||
label={option.label}
|
||||
value={option.value}
|
||||
selectedOption={selectedOption}
|
||||
onClick={handleClick}
|
||||
>{option.label}
|
||||
</DiscussionRestrictionOption>
|
||||
|
||||
))}
|
||||
</ButtonGroup>
|
||||
{(selectedOption === 'on' || selectedOption === 'off') && (
|
||||
<div className="small text-muted font-size-14 height-24 mb-4">
|
||||
{intl.formatMessage(messages.discussionRestrictionHelp)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedOption === 'on' && (
|
||||
<ConfirmationPopup
|
||||
label={intl.formatMessage(messages.enableRestrictedDatesConfirmationLabel)}
|
||||
bodyText={intl.formatMessage(messages.enableRestrictedDatesConfirmationHelp)}
|
||||
onCancel={handleCancel}
|
||||
confirmLabel={intl.formatMessage(messages.ok)}
|
||||
cancelLabel={intl.formatMessage(messages.cancelButton)}
|
||||
confirmVariant="plain"
|
||||
confirmButtonClass="bg-primary-500 text-white rounded-0 action-btn"
|
||||
cancelButtonClass="rounded-0 action-btn w-92"
|
||||
sectionClasses="card-body-section"
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedOption === 'scheduled' && (
|
||||
<div>
|
||||
<div className="small mb-3 text-muted font-size-14 height-24">
|
||||
{intl.formatMessage(messages.discussionRestrictionDatesHelp)}
|
||||
</div>
|
||||
<FieldArray
|
||||
name="restrictedDates"
|
||||
render={({ push, remove }) => (
|
||||
<div>
|
||||
{restrictedDates.map((restrictedDate, index) => (
|
||||
<DiscussionRestrictionItem
|
||||
fieldNameCommonBase={`restrictedDates.${index}`}
|
||||
restrictedDate={restrictedDate}
|
||||
key={`date-${restrictedDate.id}`}
|
||||
id={restrictedDate.id}
|
||||
onDelete={() => remove(index)}
|
||||
onClose={() => handleOnClose(index)}
|
||||
hasError={Boolean(errors?.restrictedDates?.[index])}
|
||||
/>
|
||||
))}
|
||||
<div className="mb-4 mt-4 height-36">
|
||||
<Button
|
||||
onClick={() => onAddNewItem(push)}
|
||||
variant="link"
|
||||
iconBefore={Add}
|
||||
className="text-primary-500 p-0"
|
||||
style={{ height: 28 }}
|
||||
>
|
||||
{intl.formatMessage(messages.addRestrictedDatesButton)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default injectIntl(React.memo(DiscussionRestriction));
|
||||
@@ -14,9 +14,9 @@ const CollapseCardHeading = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-2">
|
||||
{badgeStatus && <Badge variant={badgeVariant}>{badgeStatus}</Badge>}
|
||||
<div className="mt-2">{collapseHeadingText}</div>
|
||||
<div style={{ height: 72 }}>
|
||||
{badgeStatus && <Badge variant={badgeVariant} style={{ padding: '2px 8px' }}>{badgeStatus}</Badge>}
|
||||
<div className="mt-2 font-size-14" style={{ lineHeight: '24px' }}>{collapseHeadingText}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -34,4 +34,4 @@ CollapseCardHeading.defaultProps = {
|
||||
badgeStatus: '',
|
||||
};
|
||||
|
||||
export default CollapseCardHeading;
|
||||
export default React.memo(CollapseCardHeading);
|
||||
@@ -1,106 +1,116 @@
|
||||
import React, { useState } from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Form } from '@edx/paragon';
|
||||
import { useFormikContext } from 'formik';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
|
||||
import messages from '../../../messages';
|
||||
import BlackoutDatesInput from './BlackoutDatesInput';
|
||||
import { formatBlackoutDates } from '../../../utils';
|
||||
import RestrictDatesInput from './RestrictDatesInput';
|
||||
import { formatRestrictedDates } from '../../../utils';
|
||||
import {
|
||||
blackoutDatesStatus as constants,
|
||||
deleteHelperText,
|
||||
restrictedDatesStatus as constants,
|
||||
deleteRestrictedDatesHelperText,
|
||||
badgeVariant,
|
||||
} from '../../../../data/constants';
|
||||
import CollapsableEditor from '../../../../../../generic/CollapsableEditor';
|
||||
import ConfirmationPopup from '../../../../../../generic/ConfirmationPopup';
|
||||
import CollapseCardHeading from './CollapseCardHeading';
|
||||
|
||||
const BlackoutDatesItem = ({
|
||||
intl,
|
||||
blackoutDate,
|
||||
const DiscussionRestrictionItem = ({
|
||||
restrictedDate,
|
||||
onDelete,
|
||||
hasError,
|
||||
onClose,
|
||||
fieldNameCommonBase,
|
||||
}) => {
|
||||
const blackoutDateError = !blackoutDate.startDate || !blackoutDate.endDate || hasError;
|
||||
const restrictedDateError = !restrictedDate.startDate || !restrictedDate.endDate || hasError;
|
||||
const [showDeletePopup, setShowDeletePopup] = useState(false);
|
||||
const [collapseIsOpen, setCollapseOpen] = useState(blackoutDateError);
|
||||
const [collapseIsOpen, setCollapseOpen] = useState(restrictedDateError);
|
||||
const { setFieldTouched } = useFormikContext();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleToggle = (isOpen) => {
|
||||
const handleToggle = useCallback((isOpen) => {
|
||||
if (!isOpen && hasError) {
|
||||
return setCollapseOpen(true);
|
||||
}
|
||||
return setCollapseOpen(isOpen);
|
||||
};
|
||||
}, [hasError]);
|
||||
|
||||
const getHeading = (isOpen) => (
|
||||
<CollapseCardHeading
|
||||
isOpen={isOpen}
|
||||
expandHeadingText={intl.formatMessage(messages.configureBlackoutDates)}
|
||||
collapseHeadingText={formatBlackoutDates(blackoutDate)}
|
||||
badgeVariant={badgeVariant[blackoutDate.status]}
|
||||
badgeStatus={intl.formatMessage(messages.blackoutDatesStatus, {
|
||||
status: _.startCase(_.toLower(blackoutDate.status)),
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
if (showDeletePopup) {
|
||||
return (
|
||||
<ConfirmationPopup
|
||||
label={blackoutDate.status === constants.ACTIVE
|
||||
? intl.formatMessage(messages.activeBlackoutDatesDeletionLabel)
|
||||
: intl.formatMessage(messages.blackoutDatesDeletionLabel)}
|
||||
bodyText={intl.formatMessage(deleteHelperText[blackoutDate.status])}
|
||||
onConfirm={onDelete}
|
||||
confirmLabel={intl.formatMessage(messages.deleteButton)}
|
||||
onCancel={() => setShowDeletePopup(false)}
|
||||
cancelLabel={intl.formatMessage(messages.cancelButton)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const handleOnClose = () => {
|
||||
const handleOnClose = useCallback(() => {
|
||||
['startDate', 'startTime', 'endDate', 'endTime'].forEach(field => (
|
||||
setFieldTouched(`${fieldNameCommonBase}.${field}`, true)
|
||||
));
|
||||
if (!hasError) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
}, [hasError, onClose]);
|
||||
|
||||
const getHeading = useCallback((isOpen) => (
|
||||
<CollapseCardHeading
|
||||
isOpen={isOpen}
|
||||
expandHeadingText={intl.formatMessage(messages.configureRestrictedDates)}
|
||||
collapseHeadingText={formatRestrictedDates(restrictedDate)}
|
||||
badgeVariant={badgeVariant[restrictedDate.status]}
|
||||
badgeStatus={intl.formatMessage(messages.restrictedDatesStatus, {
|
||||
status: _.startCase(_.toLower(restrictedDate.status)),
|
||||
})}
|
||||
/>
|
||||
), [restrictedDate]);
|
||||
|
||||
const handleShowDeletePopup = useCallback(() => {
|
||||
setShowDeletePopup(true);
|
||||
}, []);
|
||||
|
||||
const handleCancelDeletePopup = useCallback(() => {
|
||||
setShowDeletePopup(false);
|
||||
}, []);
|
||||
|
||||
if (showDeletePopup) {
|
||||
return (
|
||||
<ConfirmationPopup
|
||||
label={restrictedDate.status === constants.ACTIVE
|
||||
? intl.formatMessage(messages.activeRestrictedDatesDeletionLabel)
|
||||
: intl.formatMessage(messages.restrictedDatesDeletionLabel)}
|
||||
bodyText={intl.formatMessage(deleteRestrictedDatesHelperText[restrictedDate.status])}
|
||||
onConfirm={onDelete}
|
||||
confirmLabel={intl.formatMessage(messages.deleteButton)}
|
||||
onCancel={handleCancelDeletePopup}
|
||||
cancelLabel={intl.formatMessage(messages.cancelButton)}
|
||||
confirmVariant="plain"
|
||||
confirmButtonClass="text-danger-500 border-gray-300 rounded-0"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CollapsableEditor
|
||||
open={collapseIsOpen}
|
||||
onToggle={handleToggle}
|
||||
title={getHeading(collapseIsOpen)}
|
||||
onDelete={() => setShowDeletePopup(true)}
|
||||
onDelete={handleShowDeletePopup}
|
||||
expandAlt={intl.formatMessage(messages.expandAltText)}
|
||||
collapseAlt={intl.formatMessage(messages.collapseAltText)}
|
||||
deleteAlt={intl.formatMessage(messages.deleteAltText)}
|
||||
data-testid={blackoutDate.id}
|
||||
onClose={() => handleOnClose()}
|
||||
data-testid={restrictedDate.id}
|
||||
onClose={handleOnClose}
|
||||
>
|
||||
<Form.Row className="mx-2 pt-3">
|
||||
<BlackoutDatesInput
|
||||
value={blackoutDate.startDate}
|
||||
<RestrictDatesInput
|
||||
value={restrictedDate.startDate}
|
||||
type="date"
|
||||
label={intl.formatMessage(messages.startDateLabel)}
|
||||
helpText={intl.formatMessage(messages.blackoutStartDateHelp)}
|
||||
helpText={intl.formatMessage(messages.restrictedStartDateHelp)}
|
||||
fieldName="startDate"
|
||||
formGroupClasses="pl-md-0"
|
||||
fieldClasses="pr-md-2"
|
||||
fieldNameCommonBase={fieldNameCommonBase}
|
||||
/>
|
||||
<BlackoutDatesInput
|
||||
value={blackoutDate.startTime}
|
||||
<RestrictDatesInput
|
||||
value={restrictedDate.startTime}
|
||||
type="time"
|
||||
label={intl.formatMessage(messages.startTimeLabel, { zone: 'UTC' })}
|
||||
helpText={intl.formatMessage(messages.blackoutStartTimeHelp)}
|
||||
helpText={intl.formatMessage(messages.restrictedStartTimeHelp)}
|
||||
fieldName="startTime"
|
||||
formGroupClasses="pr-md-0"
|
||||
fieldClasses="ml-md-2"
|
||||
@@ -110,21 +120,21 @@ const BlackoutDatesItem = ({
|
||||
</Form.Row>
|
||||
<hr className="mx-2 my-2 border-light-400" />
|
||||
<Form.Row className="mx-2 pt-4">
|
||||
<BlackoutDatesInput
|
||||
value={blackoutDate.endDate}
|
||||
<RestrictDatesInput
|
||||
value={restrictedDate.endDate}
|
||||
type="date"
|
||||
label={intl.formatMessage(messages.endDateLabel)}
|
||||
helpText={intl.formatMessage(messages.blackoutEndDateHelp)}
|
||||
helpText={intl.formatMessage(messages.restrictedEndDateHelp)}
|
||||
fieldName="endDate"
|
||||
formGroupClasses="pl-md-0"
|
||||
fieldClasses="pr-md-2"
|
||||
fieldNameCommonBase={fieldNameCommonBase}
|
||||
/>
|
||||
<BlackoutDatesInput
|
||||
value={blackoutDate.endTime}
|
||||
<RestrictDatesInput
|
||||
value={restrictedDate.endTime}
|
||||
type="time"
|
||||
label={intl.formatMessage(messages.endTimeLabel, { zone: 'UTC' })}
|
||||
helpText={intl.formatMessage(messages.blackoutEndTimeHelp)}
|
||||
helpText={intl.formatMessage(messages.restrictedEndTimeHelp)}
|
||||
fieldName="endTime"
|
||||
formGroupClasses="pr-md-0"
|
||||
fieldClasses="ml-md-2"
|
||||
@@ -136,13 +146,12 @@ const BlackoutDatesItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
BlackoutDatesItem.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
DiscussionRestrictionItem.propTypes = {
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
hasError: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
fieldNameCommonBase: PropTypes.string.isRequired,
|
||||
blackoutDate: PropTypes.shape({
|
||||
restrictedDate: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
startDate: PropTypes.string,
|
||||
endDate: PropTypes.string,
|
||||
@@ -152,4 +161,4 @@ BlackoutDatesItem.propTypes = {
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BlackoutDatesItem);
|
||||
export default React.memo(DiscussionRestrictionItem);
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const DiscussionRestrictionOption = ({
|
||||
value,
|
||||
label,
|
||||
onClick,
|
||||
selectedOption,
|
||||
}) => (
|
||||
<Button
|
||||
variant="plain"
|
||||
className={`w-100 font-size-14 line-height-20 border border-light-400 rounded-0
|
||||
${selectedOption === value ? 'text-white bg-primary-500' : 'unselected-button'}`}
|
||||
onClick={() => onClick(value)}
|
||||
style={{ padding: '8px 12px', fontWeight: 500 }}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
|
||||
DiscussionRestrictionOption.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
selectedOption: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(DiscussionRestrictionOption);
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Form } from '@edx/paragon';
|
||||
import { useFormikContext, getIn } from 'formik';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import FieldFeedback from '../../../../../../generic/FieldFeedback';
|
||||
|
||||
const BlackoutDatesInput = ({
|
||||
const RestictDatesInput = ({
|
||||
value,
|
||||
type,
|
||||
label,
|
||||
@@ -25,10 +25,14 @@ const BlackoutDatesInput = ({
|
||||
const fieldTouched = getIn(touched, `${fieldNameCommonBase}.${fieldName}`);
|
||||
const isInvalidInput = Boolean(!inFocus && fieldError && fieldTouched);
|
||||
|
||||
const handleFocusOut = (event) => {
|
||||
const handleFocusOut = useCallback((event) => {
|
||||
handleBlur(event);
|
||||
setInFocus(false);
|
||||
};
|
||||
}, [handleBlur, setInFocus]);
|
||||
|
||||
const handleSetFocus = useCallback(() => {
|
||||
setInFocus(true);
|
||||
}, [setInFocus]);
|
||||
|
||||
return (
|
||||
<Form.Group
|
||||
@@ -43,8 +47,8 @@ const BlackoutDatesInput = ({
|
||||
onChange={handleChange}
|
||||
floatingLabel={label}
|
||||
className={fieldClasses}
|
||||
onBlur={(event) => handleFocusOut(event)}
|
||||
onFocus={() => setInFocus(true)}
|
||||
onBlur={handleFocusOut}
|
||||
onFocus={handleSetFocus}
|
||||
/>
|
||||
<FieldFeedback
|
||||
feedbackCondition={inFocus}
|
||||
@@ -58,7 +62,7 @@ const BlackoutDatesInput = ({
|
||||
);
|
||||
};
|
||||
|
||||
BlackoutDatesInput.propTypes = {
|
||||
RestictDatesInput.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
fieldName: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
@@ -70,11 +74,11 @@ BlackoutDatesInput.propTypes = {
|
||||
fieldNameCommonBase: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
BlackoutDatesInput.defaultProps = {
|
||||
RestictDatesInput.defaultProps = {
|
||||
fieldClasses: '',
|
||||
helpText: '',
|
||||
feedbackClasses: '',
|
||||
formGroupClasses: '',
|
||||
};
|
||||
|
||||
export default BlackoutDatesInput;
|
||||
export default React.memo(RestictDatesInput);
|
||||
@@ -35,7 +35,7 @@ const appConfig = {
|
||||
reportedContentEmailNotifications: false,
|
||||
enableReportedContentEmailNotifications: false,
|
||||
allowDivisionByUnit: false,
|
||||
blackoutDates: [],
|
||||
restrictedDates: [],
|
||||
};
|
||||
|
||||
const contextValue = {
|
||||
|
||||
@@ -287,128 +287,141 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Choose a unique name for your topic',
|
||||
description: 'Help text for input field in adding a discussion topic',
|
||||
},
|
||||
// Blackout dates
|
||||
blackoutDates: {
|
||||
id: 'authoring.discussions.blackoutDates',
|
||||
defaultMessage: 'Discussion blackout dates',
|
||||
|
||||
},
|
||||
blackoutDatesLabel: {
|
||||
id: 'authoring.discussions.builtIn.blackoutDates.label',
|
||||
defaultMessage: 'Blackout dates',
|
||||
},
|
||||
blackoutDatesHelp: {
|
||||
id: 'authoring.discussions.builtIn.blackoutDates.help',
|
||||
defaultMessage: 'If added, learners will not be able to post in discussions between these dates.',
|
||||
},
|
||||
addBlackoutDatesButton: {
|
||||
id: 'authoring.discussions.addBlackoutDatesButton',
|
||||
defaultMessage: 'Add blackout date range',
|
||||
description: 'Button label when Add a new blackout date.',
|
||||
},
|
||||
configureBlackoutDates: {
|
||||
id: 'authoring.discussions.builtIn.configureBlackoutDates.label',
|
||||
defaultMessage: 'Configure blackout date range',
|
||||
description: 'Label for blockout dates allowing user to configure blackout dates',
|
||||
},
|
||||
blackoutStartDateHelp: {
|
||||
id: 'authoring.discussions.blackoutStartDate.help',
|
||||
// Restricted dates
|
||||
restrictedStartDateHelp: {
|
||||
id: 'authoring.discussions.restrictedStartDate.help',
|
||||
defaultMessage: 'Enter a start date, e.g. 12/10/2023',
|
||||
},
|
||||
blackoutEndDateHelp: {
|
||||
id: 'authoring.discussions.blackoutEndDate.help',
|
||||
restrictedEndDateHelp: {
|
||||
id: 'authoring.discussions.restrictedEndDate.help',
|
||||
defaultMessage: 'Enter an end date, e.g. 12/17/2023',
|
||||
},
|
||||
blackoutStartTimeHelp: {
|
||||
id: 'authoring.discussions.blackoutStartTime.help',
|
||||
restrictedStartTimeHelp: {
|
||||
id: 'authoring.discussions.restrictedStartTime.help',
|
||||
defaultMessage: 'Enter a start time, e.g. 09:00 AM',
|
||||
},
|
||||
blackoutEndTimeHelp: {
|
||||
id: 'authoring.discussions.blackoutEndTime.help',
|
||||
restrictedEndTimeHelp: {
|
||||
id: 'authoring.discussions.restrictedEndTime.help',
|
||||
defaultMessage: 'Enter an end time, e.g. 05:00 PM',
|
||||
},
|
||||
activeBlackoutDatesDeletionHelp: {
|
||||
id: 'authoring.discussions.activeBlackoutDatesDeletion.help',
|
||||
defaultMessage: 'These blackout dates are currently active. If deleted, learners will be able to post in discussions during these dates. Are you sure you want to proceed?',
|
||||
description: 'Help text for delete a active blackout dates from blackout dates section.',
|
||||
},
|
||||
blackoutDatesDeletionHelp: {
|
||||
id: 'authoring.discussions.blackoutDatesDeletion.help',
|
||||
defaultMessage: 'If deleted, learners will be able to post in discussions during these dates.',
|
||||
description: 'Help text for delete a upcoming blackout dates from blackout dates section.',
|
||||
},
|
||||
completeBlackoutDatesDeletionHelp: {
|
||||
id: 'authoring.discussions.completeBlackoutDatesDeletion.help',
|
||||
defaultMessage: 'Are you sure you want to delete these blackout dates?',
|
||||
description: 'Help text for delete a complete blackout dates from blackout dates section.',
|
||||
},
|
||||
activeBlackoutDatesDeletionLabel: {
|
||||
id: 'authoring.discussions.activeBlackoutDatesDeletion.label',
|
||||
defaultMessage: 'Delete active blackout dates?',
|
||||
description: 'Label for active blackout dates delete popup allowing a user to delete a blackout date range.',
|
||||
},
|
||||
blackoutDatesDeletionLabel: {
|
||||
id: 'authoring.discussions.blackoutDatesDeletion.label',
|
||||
defaultMessage: 'Delete blackout dates?',
|
||||
description: 'Label for blackout dates delete popup allowing a user to delete a blackout date range.',
|
||||
},
|
||||
deleteBlackoutDatesAltText: {
|
||||
id: 'authoring.blackoutDates.delete',
|
||||
defaultMessage: 'Delete Blackout Dates',
|
||||
},
|
||||
blackoutDatesStatus: {
|
||||
id: 'authoring.blackoutDates.status',
|
||||
restrictedDatesStatus: {
|
||||
id: 'authoring.restrictedDates.status',
|
||||
defaultMessage: '{status}',
|
||||
},
|
||||
blackoutStartDateRequired: {
|
||||
id: 'authoring.blackoutDates.startDate.required',
|
||||
restrictedStartDateRequired: {
|
||||
id: 'authoring.restrictedDates.startDate.required',
|
||||
defaultMessage: 'Start date is a required field',
|
||||
description: 'Tells the user that the blackout dates must have start date and it is required.',
|
||||
description: 'Tells the user that the restricted dates must have start date and it is required.',
|
||||
},
|
||||
blackoutEndDateRequired: {
|
||||
id: 'authoring.blackoutDates.endDate.required',
|
||||
restrictedEndDateRequired: {
|
||||
id: 'authoring.restrictedDates.endDate.required',
|
||||
defaultMessage: 'End date is a required field',
|
||||
description: 'Tells the user that the blackout dates must have end date and it is required.',
|
||||
description: 'Tells the user that the restricted dates must have end date and it is required.',
|
||||
},
|
||||
blackoutStartDateInPast: {
|
||||
id: 'authoring.blackoutDates.startDate.inPast',
|
||||
restrictedStartDateInPast: {
|
||||
id: 'authoring.restrictedDates.startDate.inPast',
|
||||
defaultMessage: 'Start date cannot be after end date',
|
||||
description: 'Tells the user that the blackout start date cannot be in past and cannot be after end date',
|
||||
description: 'Tells the user that the restricted start date cannot be in past and cannot be after end date',
|
||||
},
|
||||
blackoutEndDateInPast: {
|
||||
id: 'authoring.blackoutDates.endDate.inPast',
|
||||
restrictedEndDateInPast: {
|
||||
id: 'authoring.restrictedDates.endDate.inPast',
|
||||
defaultMessage: 'End date cannot be before start date',
|
||||
description: 'Tells the user that the blackout end date cannot be in past and cannot be before start date',
|
||||
description: 'Tells the user that the restricted end date cannot be in past and cannot be before start date',
|
||||
},
|
||||
blackoutStartTimeInPast: {
|
||||
id: 'authoring.blackoutDates.startTime.inPast',
|
||||
restrictedStartTimeInPast: {
|
||||
id: 'authoring.restrictedDates.startTime.inPast',
|
||||
defaultMessage: 'Start time cannot be after end time',
|
||||
description: 'Tells the user that the blackout start time cannot be in past and cannot be after end time',
|
||||
description: 'Tells the user that the restricted start time cannot be in past and cannot be after end time',
|
||||
},
|
||||
blackoutEndTimeInPast: {
|
||||
id: 'authoring.blackoutDates.endTime.inPast',
|
||||
restrictedEndTimeInPast: {
|
||||
id: 'authoring.restrictedDates.endTime.inPast',
|
||||
defaultMessage: 'End time cannot be before start time',
|
||||
description: 'Tells the user that the blackout end time cannot be in past and cannot be before start time',
|
||||
description: 'Tells the user that the restricted end time cannot be in past and cannot be before start time',
|
||||
},
|
||||
blackoutStartTimeInValidFormat: {
|
||||
id: 'authoring.blackoutDates.startTime.inValidFormat',
|
||||
restrictedStartTimeInValidFormat: {
|
||||
id: 'authoring.restrictedDates.startTime.inValidFormat',
|
||||
defaultMessage: 'Enter a valid start time',
|
||||
description: 'Tells the user that the blackout start time format is in valid',
|
||||
description: 'Tells the user that the restricted start time format is in valid',
|
||||
},
|
||||
blackoutEndTimeInValidFormat: {
|
||||
id: 'authoring.blackoutDates.endTime.inValidFormat',
|
||||
restrictedEndTimeInValidFormat: {
|
||||
id: 'authoring.restrictedDates.endTime.inValidFormat',
|
||||
defaultMessage: 'Enter a valid end time',
|
||||
description: 'Tells the user that the blackout end time format is in valid',
|
||||
description: 'Tells the user that the restricted end time format is in valid',
|
||||
},
|
||||
blackoutStartDateInValidFormat: {
|
||||
id: 'authoring.blackoutDates.startDate.inValidFormat',
|
||||
restrictedStartDateInValidFormat: {
|
||||
id: 'authoring.restrictedDates.startDate.inValidFormat',
|
||||
defaultMessage: 'Enter a valid start Date',
|
||||
description: 'Tells the user that the blackout start date format is in valid',
|
||||
description: 'Tells the user that the restricted start date format is in valid',
|
||||
},
|
||||
blackoutEndDateInValidFormat: {
|
||||
id: 'authoring.blackoutDates.endDate.inValidFormat',
|
||||
restrictedEndDateInValidFormat: {
|
||||
id: 'authoring.restrictedDates.endDate.inValidFormat',
|
||||
defaultMessage: 'Enter a valid end date',
|
||||
description: 'Tells the user that the blackout end date format is in valid',
|
||||
description: 'Tells the user that the restricted end date format is in valid',
|
||||
},
|
||||
discussionRestrictionLabel: {
|
||||
id: 'authoring.discussions.builtIn.discussionRestriction.label',
|
||||
defaultMessage: 'Discussion restrictions',
|
||||
},
|
||||
discussionRestrictionHelp: {
|
||||
id: 'authoring.discussions.discussionRestriction.help',
|
||||
defaultMessage: 'If enabled, learners will not be able to post in discussions.',
|
||||
},
|
||||
discussionRestrictionDatesHelp: {
|
||||
id: 'authoring.discussions.discussionRestrictionDates.help',
|
||||
defaultMessage: 'If added, learners will not be able to post in discussions between these dates.',
|
||||
},
|
||||
addRestrictedDatesButton: {
|
||||
id: 'authoring.discussions.addRestrictedDatesButton',
|
||||
defaultMessage: 'Add restricted dates',
|
||||
},
|
||||
configureRestrictedDates: {
|
||||
id: 'authoring.discussions.builtIn.configureRestrictedDates.label',
|
||||
defaultMessage: 'Configure restricted date range',
|
||||
},
|
||||
activeRestrictedDatesDeletionLabel: {
|
||||
id: 'authoring.discussions.activeRestrictedDatesDeletion.label',
|
||||
defaultMessage: 'Delete active restricted dates?',
|
||||
description: 'Label for active restricted dates delete popup allowing a user to delete a restricted date range.',
|
||||
},
|
||||
activeRestrictedDatesDeletionHelp: {
|
||||
id: 'authoring.discussions.activeRestrictedDatesDeletion.help',
|
||||
defaultMessage: 'These restricted dates are currently active. If deleted, learners will be able to post in discussions during these dates. Are you sure you want to proceed?',
|
||||
description: 'Help text for delete a active restricted dates from restricted dates section.',
|
||||
},
|
||||
completeRestrictedDatesDeletionHelp: {
|
||||
id: 'authoring.discussions.completeRestrictedDatesDeletion.help',
|
||||
defaultMessage: 'Are you sure you want to delete these restricted dates?',
|
||||
description: 'Help text for delete a complete restricted dates from restricted dates section.',
|
||||
},
|
||||
restrictedDatesDeletionLabel: {
|
||||
id: 'authoring.discussions.restrictedDatesDeletion.label',
|
||||
defaultMessage: 'Delete restricted dates?',
|
||||
description: 'Label for restricted dates delete popup allowing a user to delete a restricted date range.',
|
||||
},
|
||||
restrictedDatesDeletionHelp: {
|
||||
id: 'authoring.discussions.restrictedDatesDeletion.help',
|
||||
defaultMessage: 'If deleted, learners will be able to post in discussions during these dates.',
|
||||
description: 'Help text for delete a upcoming restricted dates from restricted dates section.',
|
||||
},
|
||||
discussionRestrictionOffLabel: {
|
||||
id: 'authoring.discussions.discussionRestrictionOff.label',
|
||||
defaultMessage: 'Off',
|
||||
},
|
||||
discussionRestrictionOnLabel: {
|
||||
id: 'authoring.discussions.discussionRestrictionOn.label',
|
||||
defaultMessage: 'On',
|
||||
},
|
||||
discussionRestrictionScheduledLabel: {
|
||||
id: 'authoring.discussions.discussionRestrictionScheduled.label',
|
||||
defaultMessage: 'Scheduled',
|
||||
},
|
||||
enableRestrictedDatesConfirmationLabel: {
|
||||
id: 'authoring.discussions.enableRestrictedDatesConfirmation.label',
|
||||
defaultMessage: 'Enable restricted dates?',
|
||||
},
|
||||
enableRestrictedDatesConfirmationHelp: {
|
||||
id: 'authoring.discussions.enableRestrictedDatesConfirmation.help',
|
||||
defaultMessage: 'Learners will not be able to post in discussions.',
|
||||
},
|
||||
deleteAltText: {
|
||||
id: 'authoring.topics.delete',
|
||||
@@ -423,23 +436,23 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Collapse',
|
||||
},
|
||||
startDateLabel: {
|
||||
id: 'authoring.blackoutDates.start.date',
|
||||
id: 'authoring.restrictedDates.start.date',
|
||||
defaultMessage: 'Start date',
|
||||
description: 'Label for start date field',
|
||||
},
|
||||
startTimeLabel: {
|
||||
id: 'authoring.blackoutDates.start.time',
|
||||
defaultMessage: 'Start time (optional) ({zone})',
|
||||
id: 'authoring.restrictedDates.start.time',
|
||||
defaultMessage: 'Start time (optional)',
|
||||
description: 'label for start time field',
|
||||
},
|
||||
endDateLabel: {
|
||||
id: 'authoring.blackoutDates.end.date',
|
||||
id: 'authoring.restrictedDates.end.date',
|
||||
defaultMessage: 'End date',
|
||||
description: 'label for end date field',
|
||||
},
|
||||
endTimeLabel: {
|
||||
id: 'authoring.blackoutDates.end.time',
|
||||
defaultMessage: 'End time (optional) ({zone})',
|
||||
id: 'authoring.restrictedDates.end.time',
|
||||
defaultMessage: 'End time (optional)',
|
||||
description: 'label for end time field',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import { getIn } from 'formik';
|
||||
import { blackoutDatesStatus as constants } from '../data/constants';
|
||||
import { restrictedDatesStatus as constants } from '../data/constants';
|
||||
|
||||
export const filterItemFromObject = (array, key, value) => (
|
||||
array.filter(item => item[key] !== value)
|
||||
@@ -47,7 +47,7 @@ export const decodeDateTime = (date, time) => {
|
||||
return moment(mergeDateTime(nDate, nTime));
|
||||
};
|
||||
|
||||
export const sortBlackoutDatesByStatus = (data, status, order) => (
|
||||
export const sortRestrictedDatesByStatus = (data, status, order) => (
|
||||
_.orderBy(
|
||||
data.filter(date => date.status === status),
|
||||
[(obj) => decodeDateTime(obj.startDate, startOfDayTime(obj.startTime))],
|
||||
@@ -55,7 +55,7 @@ data.filter(date => date.status === status),
|
||||
)
|
||||
);
|
||||
|
||||
export const formatBlackoutDates = ({
|
||||
export const formatRestrictedDates = ({
|
||||
startDate, startTime, endDate, endTime,
|
||||
}) => {
|
||||
let formattedDate;
|
||||
|
||||
@@ -25,3 +25,53 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.height-36{
|
||||
height: 2.25rem !important;
|
||||
}
|
||||
.line-height-20{
|
||||
line-height: 1.25rem !important;
|
||||
}
|
||||
.font-size-14{
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.discussion-restriction{
|
||||
.unselected-button{
|
||||
&:hover{
|
||||
background: #e9e6e4 !important;
|
||||
}
|
||||
}
|
||||
.action-btn{
|
||||
padding: 10px 16px;
|
||||
width: 80px;
|
||||
height: 44px;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.w-92{
|
||||
width: 92px;
|
||||
}
|
||||
.card-body-section{
|
||||
padding-top: 12px !important;
|
||||
padding-bottom: 20px !important;
|
||||
}
|
||||
.form-control{
|
||||
border-radius: 0px !important;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.collapsible-card{
|
||||
padding: 14px 14px 14px 24px !important;
|
||||
min-height:100px;
|
||||
.collapsible-trigger{
|
||||
padding: 0px !important;
|
||||
.badge{
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
mergeDateTime,
|
||||
normalizeDate,
|
||||
normalizeTime,
|
||||
sortBlackoutDatesByStatus,
|
||||
sortRestrictedDatesByStatus,
|
||||
startOfDayTime,
|
||||
} from '../app-config-form/utils';
|
||||
import { blackoutDatesStatus as constants } from './constants';
|
||||
import { restrictedDatesStatus as constants } from './constants';
|
||||
|
||||
ensureConfig([
|
||||
'STUDIO_BASE_URL',
|
||||
@@ -31,7 +31,7 @@ function normalizeLtiConfig(data) {
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeBlackoutDates(data) {
|
||||
export function normalizeRestrictedDates(data) {
|
||||
if (!data || Object.keys(data).length < 1) {
|
||||
return [];
|
||||
}
|
||||
@@ -46,9 +46,9 @@ export function normalizeBlackoutDates(data) {
|
||||
}));
|
||||
|
||||
return [
|
||||
...sortBlackoutDatesByStatus(normalizeData, constants.ACTIVE, 'desc'),
|
||||
...sortBlackoutDatesByStatus(normalizeData, constants.UPCOMING, 'asc'),
|
||||
...sortBlackoutDatesByStatus(normalizeData, constants.COMPLETE, 'desc'),
|
||||
...sortRestrictedDatesByStatus(normalizeData, constants.ACTIVE, 'desc'),
|
||||
...sortRestrictedDatesByStatus(normalizeData, constants.UPCOMING, 'asc'),
|
||||
...sortRestrictedDatesByStatus(normalizeData, constants.COMPLETE, 'desc'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ function normalizePluginConfig(data) {
|
||||
enableReportedContentEmailNotifications: data.reported_content_email_notifications_flag,
|
||||
divisionScheme: data.division_scheme,
|
||||
alwaysDivideInlineDiscussions: data.always_divide_inline_discussions,
|
||||
blackoutDates: normalizeBlackoutDates(data.discussion_blackouts),
|
||||
restrictedDates: normalizeRestrictedDates(data.discussion_blackouts),
|
||||
allowDivisionByUnit: false,
|
||||
divideByCohorts: enableDivideByCohorts,
|
||||
divideCourseTopicsByCohorts: enableDivideCourseTopicsByCohorts,
|
||||
@@ -155,15 +155,15 @@ function normalizeSettings(data) {
|
||||
};
|
||||
}
|
||||
|
||||
export function denormalizeBlackoutDate(blackoutPeriod) {
|
||||
export function denormalizeRestrictedDate(restrictedPeriod) {
|
||||
return [
|
||||
mergeDateTime(
|
||||
normalizeDate(blackoutPeriod.startDate),
|
||||
normalizeTime(startOfDayTime(blackoutPeriod.startTime)),
|
||||
normalizeDate(restrictedPeriod.startDate),
|
||||
normalizeTime(startOfDayTime(restrictedPeriod.startTime)),
|
||||
),
|
||||
mergeDateTime(
|
||||
normalizeDate(blackoutPeriod.endDate),
|
||||
normalizeTime(endOfDayTime(blackoutPeriod.endTime)),
|
||||
normalizeDate(restrictedPeriod.endDate),
|
||||
normalizeTime(endOfDayTime(restrictedPeriod.endTime)),
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -187,11 +187,11 @@ function denormalizeData(courseId, appId, data) {
|
||||
if ('groupAtSubsection' in data) {
|
||||
pluginConfiguration.group_at_subsection = data.groupAtSubsection;
|
||||
}
|
||||
if (data.blackoutDates?.length) {
|
||||
pluginConfiguration.discussion_blackouts = data.blackoutDates.map((blackoutDates) => (
|
||||
denormalizeBlackoutDate(blackoutDates)
|
||||
if (data.restrictedDates?.length) {
|
||||
pluginConfiguration.discussion_blackouts = data.restrictedDates.map((restrictedDates) => (
|
||||
denormalizeRestrictedDate(restrictedDates)
|
||||
));
|
||||
} else if (data.blackoutDates?.length === 0) {
|
||||
} else if (data.restrictedDates?.length === 0) {
|
||||
pluginConfiguration.discussion_blackouts = [];
|
||||
}
|
||||
if (data.discussionTopics?.length) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import moment from 'moment';
|
||||
|
||||
import messages from '../app-config-form/messages';
|
||||
|
||||
export const blackoutDatesStatus = {
|
||||
export const restrictedDatesStatus = {
|
||||
UPCOMING: 'UPCOMING',
|
||||
COMPLETE: 'COMPLETE',
|
||||
ACTIVE: 'ACTIVE',
|
||||
@@ -14,12 +14,42 @@ export const badgeVariant = {
|
||||
ACTIVE: 'success',
|
||||
};
|
||||
|
||||
export const deleteHelperText = {
|
||||
UPCOMING: messages.blackoutDatesDeletionHelp,
|
||||
COMPLETE: messages.completeBlackoutDatesDeletionHelp,
|
||||
ACTIVE: messages.activeBlackoutDatesDeletionHelp,
|
||||
export const deleteRestrictedDatesHelperText = {
|
||||
UPCOMING: messages.restrictedDatesDeletionHelp,
|
||||
COMPLETE: messages.completeRestrictedDatesDeletionHelp,
|
||||
ACTIVE: messages.activeRestrictedDatesDeletionHelp,
|
||||
};
|
||||
|
||||
export const discussionRestriction = {
|
||||
OFF: 'off',
|
||||
ON: 'on',
|
||||
SCHEDULED: 'scheduled',
|
||||
};
|
||||
|
||||
export const discussionRestrictionLabel = {
|
||||
[discussionRestriction.OFF]: 'Off',
|
||||
[discussionRestriction.ON]: 'On',
|
||||
[discussionRestriction.SCHEDULED]: 'Scheduled',
|
||||
};
|
||||
|
||||
export const discussionRestrictionOptions = [
|
||||
{
|
||||
value: discussionRestriction.OFF,
|
||||
description: messages.discussionRestrictionOffLabel,
|
||||
label: discussionRestrictionLabel[discussionRestriction.OFF],
|
||||
},
|
||||
{
|
||||
value: discussionRestriction.ON,
|
||||
description: messages.discussionRestrictionOnLabel,
|
||||
label: discussionRestrictionLabel[discussionRestriction.ON],
|
||||
},
|
||||
{
|
||||
value: discussionRestriction.SCHEDULED,
|
||||
description: messages.discussionRestrictionScheduledLabel,
|
||||
label: discussionRestrictionLabel[discussionRestriction.SCHEDULED],
|
||||
},
|
||||
];
|
||||
|
||||
export const today = moment();
|
||||
export const active = [today.format('YYYY-MM-DDTHH:mm'), today.add(5, 'hours').format('YYYY-MM-DDTHH:mm')];
|
||||
export const upcoming = [today.add(2, 'days').format('YYYY-MM-DD'), today.add(5, 'days').format('YYYY-MM-DD')];
|
||||
|
||||
@@ -244,7 +244,7 @@ describe('Data layer integration tests', () => {
|
||||
allowAnonymousPostsPeers: false,
|
||||
reportedContentEmailNotifications: false,
|
||||
enableReportedContentEmailNotifications: false,
|
||||
blackoutDates: [],
|
||||
restrictedDates: [],
|
||||
// TODO: Note! As of this writing, all the data below this line is NOT returned in the API
|
||||
// but we add it in during normalization.
|
||||
divisionScheme: DivisionSchemes.COHORT,
|
||||
@@ -449,7 +449,7 @@ describe('Data layer integration tests', () => {
|
||||
allowAnonymousPosts: true,
|
||||
allowAnonymousPostsPeers: true,
|
||||
reportedContentEmailNotifications: true,
|
||||
blackoutDates: [],
|
||||
restrictedDates: [],
|
||||
// TODO: Note! As of this writing, all the data below this line is NOT returned in the API
|
||||
// but we technically send it to the thunk, so here it is.
|
||||
divideByCohorts: true,
|
||||
@@ -486,7 +486,7 @@ describe('Data layer integration tests', () => {
|
||||
allowAnonymousPostsPeers: true,
|
||||
reportedContentEmailNotifications: true,
|
||||
alwaysDivideInlineDiscussions: true,
|
||||
blackoutDates: [],
|
||||
restrictedDates: [],
|
||||
// TODO: Note! The values we tried to save were ignored, this test reflects what currently
|
||||
// happens, but NOT what we want to have happen!
|
||||
divideByCohorts: true,
|
||||
|
||||
Reference in New Issue
Block a user