diff --git a/src/course-home/progress-tab/Chapter.jsx b/src/course-home/progress-tab/Chapter.jsx new file mode 100644 index 00000000..74c00343 --- /dev/null +++ b/src/course-home/progress-tab/Chapter.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Subsection from './Subsection'; + +export default function Chapter({ + chapter, +}) { + if (chapter.displayName === 'hidden') { return null; } + const { subsections } = chapter; + return ( +
+
+
+ {chapter.displayName} +
+
+ {subsections.map((subsection) => ( + + ))} +
+
+
+ ); +} + +Chapter.propTypes = { + chapter: PropTypes.shape({ + displayName: PropTypes.string, + subsections: PropTypes.arrayOf(PropTypes.shape({ + url: PropTypes.string, + })), + }).isRequired, +}; diff --git a/src/course-home/progress-tab/DueDateTime.jsx b/src/course-home/progress-tab/DueDateTime.jsx new file mode 100644 index 00000000..81d80e7d --- /dev/null +++ b/src/course-home/progress-tab/DueDateTime.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { FormattedDate, FormattedTime } from '@edx/frontend-platform/i18n'; +import { useModel } from '../../generic/model-store'; + +export default function DueDateTime({ + due, +}) { + const { + courseId, + } = useSelector(state => state.courseHome); + const { + userTimezone, + } = useModel('progress', courseId); + const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {}; + + return ( + + due + + ); +} + +DueDateTime.propTypes = { + due: PropTypes.string.isRequired, +}; diff --git a/src/course-home/progress-tab/ProblemScores.jsx b/src/course-home/progress-tab/ProblemScores.jsx new file mode 100644 index 00000000..22cf9084 --- /dev/null +++ b/src/course-home/progress-tab/ProblemScores.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import messages from './messages'; + +function ProblemScores({ + intl, + scoreName, + problemScores, +}) { + return ( +
+
+
{intl.formatMessage(messages[`${scoreName}`])}
+ {problemScores.map((problem, index) => { + const key = scoreName + index; + return ( +
{problem.earned}/{problem.possible}
+ ); + })} +
+
+ ); +} + +ProblemScores.propTypes = { + intl: intlShape.isRequired, + scoreName: PropTypes.string.isRequired, + problemScores: PropTypes.arrayOf(PropTypes.shape({ + possible: PropTypes.number, + earned: PropTypes.number, + id: PropTypes.string, + })).isRequired, +}; + +export default injectIntl(ProblemScores); diff --git a/src/course-home/progress-tab/ProgressTab.jsx b/src/course-home/progress-tab/ProgressTab.jsx index 247a96d0..e9489755 100644 --- a/src/course-home/progress-tab/ProgressTab.jsx +++ b/src/course-home/progress-tab/ProgressTab.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { useModel } from '../../generic/model-store'; +import Chapter from './Chapter'; export default function ProgressTab() { const { @@ -12,15 +13,18 @@ export default function ProgressTab() { const { enrollmentMode, + coursewareSummary, } = useModel('progress', courseId); return (
-

- the user is {username} and they are enrolled as an {enrollmentMode} learner - {administrator - &&
the user is admin
} -

+ {enrollmentMode} {administrator} {username} + {coursewareSummary.map((chapter) => ( + + ))}
); } diff --git a/src/course-home/progress-tab/Subsection.jsx b/src/course-home/progress-tab/Subsection.jsx new file mode 100644 index 00000000..46c5f4b8 --- /dev/null +++ b/src/course-home/progress-tab/Subsection.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import messages from './messages'; + +import DueDateTime from './DueDateTime'; +import ProblemScores from './ProblemScores'; + +function Subsection({ + intl, + subsection, +}) { + const scoreName = subsection.graded ? 'problem' : 'practice'; + + const { earned, possible } = subsection.gradedTotal; + + const showTotalScore = ((possible > 0) || (earned > 0)) && subsection.showGrades; + + // screen reader information + const totalScoreSr = intl.formatMessage(messages.pointsEarned, { earned, total: possible }); + + return ( +
+
+ +
+ {showTotalScore && {totalScoreSr}} + + {showTotalScore && ({earned}/{possible}) {subsection.percentGraded}%} +
+
+ {subsection.format &&
{subsection.format}
} + {subsection.due !== null && } +
+ {subsection.problemScores.length > 0 && subsection.showGrades && ( + + )} + {subsection.problemScores.length > 0 && !subsection.showGrades && subsection.showCorrectness === 'past_due' && ( +
{intl.formatMessage(messages[`${scoreName}HiddenUntil`])}
+ )} + {subsection.problemScores.length > 0 && !subsection.showGrades && !(subsection.showCorrectness === 'past_due') + &&
{intl.formatMessage(messages[`${scoreName}Hidden`])}
} + {(subsection.problemScores.length === 0) && ( +
{intl.formatMessage(messages.noScores)}
+ )} +
+ ); +} + +Subsection.propTypes = { + intl: intlShape.isRequired, + subsection: PropTypes.shape({ + graded: PropTypes.bool.isRequired, + url: PropTypes.string.isRequired, + showGrades: PropTypes.bool.isRequired, + gradedTotal: PropTypes.shape({ + possible: PropTypes.number, + earned: PropTypes.number, + graded: PropTypes.bool, + }).isRequired, + showCorrectness: PropTypes.string.isRequired, + due: PropTypes.string, + problemScores: PropTypes.arrayOf(PropTypes.shape({ + possible: PropTypes.number, + earned: PropTypes.number, + id: PropTypes.string, + })).isRequired, + format: PropTypes.string, + // override: PropTypes.object, + displayName: PropTypes.string.isRequired, + percentGraded: PropTypes.number.isRequired, + }).isRequired, +}; + +export default injectIntl(Subsection); diff --git a/src/course-home/progress-tab/messages.js b/src/course-home/progress-tab/messages.js new file mode 100644 index 00000000..9a81a510 --- /dev/null +++ b/src/course-home/progress-tab/messages.js @@ -0,0 +1,38 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + problem: { + id: 'learning.progress.badge.problem', + defaultMessage: 'Problem Scores: ', + }, + practice: { + id: 'learning.progress.badge.practice', + defaultMessage: 'Practice Scores: ', + }, + problemHiddenUntil: { + id: 'learning.progress.badge.problemHiddenUntil', + defaultMessage: 'Problem scores are hidden until the due date.', + }, + practiceHiddenUntil: { + id: 'learning.progress.badge.practiceHiddenUntil', + defaultMessage: 'Practice scores are hidden until the due date.', + }, + problemHidden: { + id: 'learning.progress.badge.probHidden', + defaultMessage: 'problemlem scores are hidden.', + }, + practiceHidden: { + id: 'learning.progress.badge.practiceHidden', + defaultMessage: 'Practice scores are hidden.', + }, + noScores: { + id: 'learning.progress.badge.noScores', + defaultMessage: 'No problem scores in this section.', + }, + pointsEarned: { + id: 'learning.progress.badge.scoreEarned', + defaultMessage: '{earned} of {total} possible points', + }, +}); + +export default messages;