From 0d67c2588d546929a866caab38fb017f3961a6e7 Mon Sep 17 00:00:00 2001 From: sundasnoreen12 <72802712+sundasnoreen12@users.noreply.github.com> Date: Fri, 19 May 2023 05:16:48 -0700 Subject: [PATCH] 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 Co-authored-by: SundasNoreen --- src/generic/CollapsableEditor.jsx | 2 +- src/generic/ConfirmationPopup.jsx | 22 +- src/generic/FieldFeedback.jsx | 2 +- .../apps/openedx/OpenedXConfigForm.jsx | 40 ++-- .../apps/openedx/OpenedXConfigForm.test.jsx | 6 +- .../apps/shared/BlackoutDatesField.jsx | 95 -------- .../apps/shared/DiscussionRestriction.jsx | 137 ++++++++++++ .../CollapseCardHeading.jsx | 8 +- .../DiscussionRestrictionItem.jsx} | 131 ++++++----- .../DiscussionRestrictionOption.jsx | 29 +++ .../RestrictDatesInput.jsx} | 22 +- .../DiscussionTopics.test.jsx | 2 +- .../discussions/app-config-form/messages.js | 211 ++++++++++-------- .../discussions/app-config-form/utils.js | 6 +- .../discussions/app-list/AppList.scss | 50 +++++ .../discussions/data/api.js | 32 +-- .../discussions/data/constants.js | 40 +++- .../discussions/data/redux.test.js | 6 +- 18 files changed, 516 insertions(+), 325 deletions(-) delete mode 100644 src/pages-and-resources/discussions/app-config-form/apps/shared/BlackoutDatesField.jsx create mode 100644 src/pages-and-resources/discussions/app-config-form/apps/shared/DiscussionRestriction.jsx rename src/pages-and-resources/discussions/app-config-form/apps/shared/{blackout-dates => discussion-restrictions}/CollapseCardHeading.jsx (70%) rename src/pages-and-resources/discussions/app-config-form/apps/shared/{blackout-dates/BlackoutDatesItem.jsx => discussion-restrictions/DiscussionRestrictionItem.jsx} (56%) create mode 100644 src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/DiscussionRestrictionOption.jsx rename src/pages-and-resources/discussions/app-config-form/apps/shared/{blackout-dates/BlackoutDatesInput.jsx => discussion-restrictions/RestrictDatesInput.jsx} (81%) diff --git a/src/generic/CollapsableEditor.jsx b/src/generic/CollapsableEditor.jsx index 113ba22db..d1e451767 100644 --- a/src/generic/CollapsableEditor.jsx +++ b/src/generic/CollapsableEditor.jsx @@ -79,4 +79,4 @@ CollapsableEditor.defaultProps = { }, }; -export default CollapsableEditor; +export default React.memo(CollapsableEditor); diff --git a/src/generic/ConfirmationPopup.jsx b/src/generic/ConfirmationPopup.jsx index 754491466..43e57528f 100644 --- a/src/generic/ConfirmationPopup.jsx +++ b/src/generic/ConfirmationPopup.jsx @@ -9,6 +9,10 @@ const ConfirmationPopup = ({ confirmLabel, onCancel, cancelLabel, + confirmVariant, + confirmButtonClass, + cancelButtonClass, + sectionClasses, }) => ( - {bodyText} + {bodyText} - - @@ -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); diff --git a/src/generic/FieldFeedback.jsx b/src/generic/FieldFeedback.jsx index f0214ef16..58373f763 100644 --- a/src/generic/FieldFeedback.jsx +++ b/src/generic/FieldFeedback.jsx @@ -50,4 +50,4 @@ FieldFeedback.defaultProps = { errorMessage: '', }; -export default FieldFeedback; +export default React.memo(FieldFeedback); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/openedx/OpenedXConfigForm.jsx b/src/pages-and-resources/discussions/app-config-form/apps/openedx/OpenedXConfigForm.jsx index 5eb97ce13..8ff66c8dd 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/openedx/OpenedXConfigForm.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/openedx/OpenedXConfigForm.jsx @@ -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 = ({ - + diff --git a/src/pages-and-resources/discussions/app-config-form/apps/openedx/OpenedXConfigForm.test.jsx b/src/pages-and-resources/discussions/app-config-form/apps/openedx/OpenedXConfigForm.test.jsx index 19dd45fb1..7efa06450 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/openedx/OpenedXConfigForm.test.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/openedx/OpenedXConfigForm.test.jsx @@ -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 () => { diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/BlackoutDatesField.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/BlackoutDatesField.jsx deleted file mode 100644 index 3c6341bf2..000000000 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/BlackoutDatesField.jsx +++ /dev/null @@ -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 ( - <> -
- {intl.formatMessage(messages.blackoutDatesLabel)} -
- -
- {intl.formatMessage(messages.blackoutDatesHelp)} -
-
- ( -
- {blackoutDates.map((blackoutDate, index) => ( - remove(index)} - onClose={() => handleOnClose(index)} - hasError={Boolean(errors?.blackoutDates?.[index])} - /> - ))} -
- -
-
- )} - /> -
- - ); -}; - -BlackoutDatesField.propTypes = { - intl: intlShape.isRequired, -}; - -export default injectIntl(BlackoutDatesField); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/DiscussionRestriction.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/DiscussionRestriction.jsx new file mode 100644 index 000000000..691993268 --- /dev/null +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/DiscussionRestriction.jsx @@ -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 ( +
+
+ {intl.formatMessage(messages.discussionRestrictionLabel)} +
+ + {discussionRestrictionOptions.map((option) => ( + {option.label} + + + ))} + + {(selectedOption === 'on' || selectedOption === 'off') && ( +
+ {intl.formatMessage(messages.discussionRestrictionHelp)} +
+ )} + + {selectedOption === 'on' && ( + + )} + + {selectedOption === 'scheduled' && ( +
+
+ {intl.formatMessage(messages.discussionRestrictionDatesHelp)} +
+ ( +
+ {restrictedDates.map((restrictedDate, index) => ( + remove(index)} + onClose={() => handleOnClose(index)} + hasError={Boolean(errors?.restrictedDates?.[index])} + /> + ))} +
+ +
+
+ )} + /> +
+ )} +
+ ); +}; + +export default injectIntl(React.memo(DiscussionRestriction)); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/CollapseCardHeading.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/CollapseCardHeading.jsx similarity index 70% rename from src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/CollapseCardHeading.jsx rename to src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/CollapseCardHeading.jsx index 428516047..8bd6c8df1 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/CollapseCardHeading.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/CollapseCardHeading.jsx @@ -14,9 +14,9 @@ const CollapseCardHeading = ({ } return ( -
- {badgeStatus && {badgeStatus}} -
{collapseHeadingText}
+
+ {badgeStatus && {badgeStatus}} +
{collapseHeadingText}
); }; @@ -34,4 +34,4 @@ CollapseCardHeading.defaultProps = { badgeStatus: '', }; -export default CollapseCardHeading; +export default React.memo(CollapseCardHeading); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/BlackoutDatesItem.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/DiscussionRestrictionItem.jsx similarity index 56% rename from src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/BlackoutDatesItem.jsx rename to src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/DiscussionRestrictionItem.jsx index 32b1317dc..e7661cf53 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/BlackoutDatesItem.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/DiscussionRestrictionItem.jsx @@ -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) => ( - - ); - - if (showDeletePopup) { - return ( - 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) => ( + + ), [restrictedDate]); + + const handleShowDeletePopup = useCallback(() => { + setShowDeletePopup(true); + }, []); + + const handleCancelDeletePopup = useCallback(() => { + setShowDeletePopup(false); + }, []); + + if (showDeletePopup) { + return ( + + ); + } return ( 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} > - -
- - ( + + ); + +DiscussionRestrictionOption.propTypes = { + value: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + selectedOption: PropTypes.string.isRequired, +}; + +export default React.memo(DiscussionRestrictionOption); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/BlackoutDatesInput.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/RestrictDatesInput.jsx similarity index 81% rename from src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/BlackoutDatesInput.jsx rename to src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/RestrictDatesInput.jsx index 0c88a132c..1509d794d 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/blackout-dates/BlackoutDatesInput.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-restrictions/RestrictDatesInput.jsx @@ -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 ( handleFocusOut(event)} - onFocus={() => setInFocus(true)} + onBlur={handleFocusOut} + onFocus={handleSetFocus} /> ( 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; diff --git a/src/pages-and-resources/discussions/app-list/AppList.scss b/src/pages-and-resources/discussions/app-list/AppList.scss index 40e2a3c73..3aa109914 100644 --- a/src/pages-and-resources/discussions/app-list/AppList.scss +++ b/src/pages-and-resources/discussions/app-list/AppList.scss @@ -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; + } + } + } +} diff --git a/src/pages-and-resources/discussions/data/api.js b/src/pages-and-resources/discussions/data/api.js index 0b272c474..20122c7f7 100644 --- a/src/pages-and-resources/discussions/data/api.js +++ b/src/pages-and-resources/discussions/data/api.js @@ -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) { diff --git a/src/pages-and-resources/discussions/data/constants.js b/src/pages-and-resources/discussions/data/constants.js index 98215bb2f..a87c48639 100644 --- a/src/pages-and-resources/discussions/data/constants.js +++ b/src/pages-and-resources/discussions/data/constants.js @@ -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')]; diff --git a/src/pages-and-resources/discussions/data/redux.test.js b/src/pages-and-resources/discussions/data/redux.test.js index cc464db36..9eb72e023 100644 --- a/src/pages-and-resources/discussions/data/redux.test.js +++ b/src/pages-and-resources/discussions/data/redux.test.js @@ -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,