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 (
+
+
+
+ {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;