diff --git a/src/App.jsx b/src/App.jsx
index 6a3cc588..d135fb0e 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -10,6 +10,7 @@ import SelfAssessmentView from 'views/SelfAssessmentView';
import StudentTrainingView from 'views/StudentTrainingView';
import SubmissionView from 'views/SubmissionView';
import XBlockView from 'views/XBlockView';
+import GradeView from 'views/GradeView';
import AppContainer from 'components/AppContainer';
import ModalContainer from 'components/ModalContainer';
@@ -49,6 +50,7 @@ const RouterRoot = () => {
modalRoute(routes.selfAssessmentEmbed, SelfAssessmentView, 'ORA Self Assessment'),
modalRoute(routes.studentTrainingEmbed, StudentTrainingView, 'ORA Student Training'),
modalRoute(routes.submissionEmbed, SubmissionView, 'ORA Submission'),
+ modalRoute(routes.gradedEmbed, GradeView, 'My Grade'),
} />,
];
const baseRoutes = [
@@ -57,6 +59,7 @@ const RouterRoot = () => {
modalRoute(routes.selfAssessment, SelfAssessmentView, 'Assess yourself'),
modalRoute(routes.studentTraining, StudentTrainingView, 'Practice grading'),
modalRoute(routes.submission, SubmissionView, 'Your response'),
+ modalRoute(routes.graded, GradeView, 'My Grade'),
} />,
];
diff --git a/src/components/CollapsibleFeedback/AssessmentCriterion.jsx b/src/components/CollapsibleFeedback/AssessmentCriterion.jsx
new file mode 100644
index 00000000..4fb17958
--- /dev/null
+++ b/src/components/CollapsibleFeedback/AssessmentCriterion.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+import Feedback from './Feedback';
+import messages from './messages';
+import { useORAConfigData } from 'data/services/lms/hooks/selectors';
+
+const AssessmentCriterion = ({
+ assessmentCriterions,
+ overallFeedback,
+ stepLabel,
+}) => {
+ const { formatMessage } = useIntl();
+ const { rubricConfig } = useORAConfigData();
+ return (
+ <>
+ {rubricConfig.criteria.map((criterion, i) => {
+ const assessmentCriterion = assessmentCriterions[i];
+ const option = criterion.options[assessmentCriterion.selectedOption];
+ return (
+
+ );
+ })}
+
+ >
+ );
+};
+AssessmentCriterion.defaultProps = {};
+AssessmentCriterion.propTypes = {
+ assessmentCriterions: PropTypes.arrayOf(PropTypes.shape({
+ selectedOption: PropTypes.number,
+ // selectedPoints: PropTypes.number,
+ feedback: PropTypes.string,
+ })),
+ overallFeedback: PropTypes.string,
+ stepLabel: PropTypes.string.isRequired,
+};
+
+export default AssessmentCriterion;
diff --git a/src/components/CollapsibleFeedback/CollapsibleFeedback.jsx b/src/components/CollapsibleFeedback/CollapsibleFeedback.jsx
new file mode 100644
index 00000000..cd48b2ff
--- /dev/null
+++ b/src/components/CollapsibleFeedback/CollapsibleFeedback.jsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Collapsible } from '@edx/paragon';
+import { useIntl } from '@edx/frontend-platform/i18n';
+import messages from './messages';
+
+const CollapsibleFeedback = ({ children, stepScore, stepLabel, defaultOpen }) => {
+ const { formatMessage } = useIntl();
+ const [open, setOpen] = React.useState(defaultOpen);
+
+ const toggle = () => setOpen(!open);
+
+ return (
+
+ {formatMessage(messages.grade, { stepLabel })}
+ {stepScore && formatMessage(messages.gradePoints, stepScore)}
+
+ }
+ open={open}
+ onToggle={toggle}
+ >
+ {children}
+
+ );
+};
+CollapsibleFeedback.defaultProps = {};
+CollapsibleFeedback.propTypes = {
+ stepLabel: PropTypes.string.isRequired,
+ stepScore: PropTypes.shape({
+ earned: PropTypes.number,
+ possible: PropTypes.number,
+ }),
+ children: PropTypes.node.isRequired,
+ defaultOpen: PropTypes.bool,
+};
+
+export default CollapsibleFeedback;
diff --git a/src/components/CollapsibleFeedback/Feedback.jsx b/src/components/CollapsibleFeedback/Feedback.jsx
new file mode 100644
index 00000000..5ac7137c
--- /dev/null
+++ b/src/components/CollapsibleFeedback/Feedback.jsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { Collapsible, Icon } from '@edx/paragon';
+import { ExpandMore, ExpandLess } from '@edx/paragon/icons';
+import { useIntl } from '@edx/frontend-platform/i18n';
+import messages from './messages';
+import InfoPopover from 'components/InfoPopover';
+
+const Feedback = ({
+ criterionName,
+ criterionDescription,
+ selectedOption,
+ selectedPoints,
+ commentHeader,
+ commentBody,
+ defaultOpen,
+}) => {
+ const [isExpanded, setIsExpanded] = React.useState(defaultOpen);
+ const { formatMessage } = useIntl();
+
+ const toggle = () => setIsExpanded(!isExpanded);
+
+ return (
+ <>
+
+
+
{criterionName}
+ {criterionDescription && (
+
{}}>
+ {criterionDescription}
+
+ )}
+
+ {selectedOption && (
+
+ {selectedOption} -- {selectedPoints} points
+
+ )}
+
+
+
+
+ {commentHeader} Comment
+ {isExpanded ? (
+
+ {formatMessage(messages.readLess)}
+
+
+ ) : (
+
+ {formatMessage(messages.readMore)}
+
+
+ )}
+
+
+ {commentBody}
+
+
+
+ >
+ );
+};
+Feedback.defaultProps = {
+ defaultOpen: false,
+};
+Feedback.propTypes = {
+ defaultOpen: PropTypes.bool,
+ criterionName: PropTypes.string.isRequired,
+ criterionDescription: PropTypes.string,
+ selectedOption: PropTypes.string,
+ selectedPoints: PropTypes.number,
+ commentHeader: PropTypes.string.isRequired,
+ commentBody: PropTypes.string.isRequired,
+};
+
+export default Feedback;
diff --git a/src/components/CollapsibleFeedback/MultipleAssessmentStep.jsx b/src/components/CollapsibleFeedback/MultipleAssessmentStep.jsx
new file mode 100644
index 00000000..9f5c7d04
--- /dev/null
+++ b/src/components/CollapsibleFeedback/MultipleAssessmentStep.jsx
@@ -0,0 +1,48 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+
+import CollapsibleFeedback from './CollapsibleFeedback';
+import AssessmentCriterion from './AssessmentCriterion';
+
+const MultipleAssessmentStep = ({
+ stepLabel,
+ step,
+ stepScore,
+ assessments,
+ defaultOpen,
+}) => (
+
+
+ {assessments?.map((assessment, index) => (
+
+
+ {stepLabel} {index + 1}:
+
+
+
+
+ ))}
+
+
+);
+
+MultipleAssessmentStep.defaultProps = {
+ defaultOpen: false,
+};
+MultipleAssessmentStep.propTypes = {
+ stepLabel: PropTypes.string.isRequired,
+ stepScore: PropTypes.shape({
+ earned: PropTypes.number,
+ possible: PropTypes.number,
+ }),
+ assessments: PropTypes.arrayOf(
+ PropTypes.shape({
+ selectedOption: PropTypes.number,
+ // selectedPoints: PropTypes.number,
+ feedback: PropTypes.string,
+ })
+ ),
+ defaultOpen: PropTypes.bool,
+};
+
+export default MultipleAssessmentStep;
diff --git a/src/components/CollapsibleFeedback/SingleAssessmentStep.jsx b/src/components/CollapsibleFeedback/SingleAssessmentStep.jsx
new file mode 100644
index 00000000..ffb830ce
--- /dev/null
+++ b/src/components/CollapsibleFeedback/SingleAssessmentStep.jsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import CollapsibleFeedback from './CollapsibleFeedback';
+import AssessmentCriterion from './AssessmentCriterion';
+
+const SingleAssessmentStep = ({
+ stepLabel,
+ step,
+ stepScore,
+ assessment,
+ defaultOpen,
+}) => (
+
+
+
+);
+
+SingleAssessmentStep.defaultProps = {
+ defaultOpen: false,
+};
+SingleAssessmentStep.propTypes = {
+ stepLabel: PropTypes.string.isRequired,
+ stepScore: PropTypes.shape({
+ earned: PropTypes.number,
+ possible: PropTypes.number,
+ }),
+ assessment: PropTypes.shape({
+ selectedOption: PropTypes.number,
+ // selectedPoints: PropTypes.number,
+ feedback: PropTypes.string,
+ }),
+ defaultOpen: PropTypes.bool,
+};
+
+export default SingleAssessmentStep;
diff --git a/src/components/CollapsibleFeedback/index.jsx b/src/components/CollapsibleFeedback/index.jsx
new file mode 100644
index 00000000..d84328df
--- /dev/null
+++ b/src/components/CollapsibleFeedback/index.jsx
@@ -0,0 +1,5 @@
+export { default as CollapsibleFeedback } from './CollapsibleFeedback';
+export { default as MultipleAssessmentStep } from './MultipleAssessmentStep';
+export { default as SingleAssessmentStep } from './SingleAssessmentStep';
+export { default as AssessmentCriterion } from './AssessmentCriterion';
+export { default as Feedback } from './Feedback';
\ No newline at end of file
diff --git a/src/components/CollapsibleFeedback/messages.js b/src/components/CollapsibleFeedback/messages.js
new file mode 100644
index 00000000..b6dadfc3
--- /dev/null
+++ b/src/components/CollapsibleFeedback/messages.js
@@ -0,0 +1,36 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ readMore: {
+ id: 'ora-collapsible-comment.readMore',
+ defaultMessage: 'Read more',
+ description: 'Read more button text',
+ },
+ readLess: {
+ id: 'ora-collapsible-comment.readLess',
+ defaultMessage: 'Read less',
+ description: 'Read less button text',
+ },
+ grade: {
+ id: 'ora-collapsible-comment.grade',
+ defaultMessage: '{stepLabel} Grade:',
+ description: 'Grade button text',
+ },
+ gradePoints: {
+ id: 'ora-collapsible-comment.gradePoints',
+ defaultMessage: '{earned} / {possible}',
+ description: 'Grade points button text',
+ },
+ notWeightedGradeLabel: {
+ id: 'ora-collapsible-comment.notWeightedGradeLabel',
+ defaultMessage: '(Not weighted toward final grade))',
+ description: 'Not weighted grade label',
+ },
+ overallFeedback: {
+ id: 'ora-collapsible-comment.overallFeedback',
+ defaultMessage: 'Overall Feedback',
+ description: 'Overall feedback label',
+ },
+});
+
+export default messages;
diff --git a/src/components/FilePreview/components/FileRenderer/FileCard/index.jsx b/src/components/FilePreview/components/FileRenderer/FileCard/index.jsx
index 9b622883..d84be372 100644
--- a/src/components/FilePreview/components/FileRenderer/FileCard/index.jsx
+++ b/src/components/FilePreview/components/FileRenderer/FileCard/index.jsx
@@ -8,11 +8,11 @@ import './FileCard.scss';
/**
*
*/
-const FileCard = ({ file, children }) => (
+const FileCard = ({ file, children, defaultOpen }) => (
{file.fileName}}
>
@@ -24,6 +24,7 @@ const FileCard = ({ file, children }) => (
FileCard.propTypes = {
file: PropTypes.shape({ fileName: PropTypes.string.isRequired }).isRequired,
children: PropTypes.node.isRequired,
+ defaultOpen: PropTypes.bool.isRequired,
};
export default FileCard;
diff --git a/src/components/FilePreview/components/FileRenderer/index.jsx b/src/components/FilePreview/components/FileRenderer/index.jsx
index 679e664e..5120ea82 100644
--- a/src/components/FilePreview/components/FileRenderer/index.jsx
+++ b/src/components/FilePreview/components/FileRenderer/index.jsx
@@ -10,7 +10,7 @@ import { useRenderData } from './hooks';
/**
*
*/
-export const FileRenderer = ({ file }) => {
+export const FileRenderer = ({ file, defaultOpen }) => {
const { formatMessage } = useIntl();
const {
Renderer,
@@ -21,7 +21,7 @@ export const FileRenderer = ({ file }) => {
} = useRenderData({ file, formatMessage });
return (
-
+
{isLoading && }
{errorStatus ? (
@@ -32,12 +32,15 @@ export const FileRenderer = ({ file }) => {
);
};
-FileRenderer.defaultProps = {};
+FileRenderer.defaultProps = {
+ defaultOpen: true,
+};
FileRenderer.propTypes = {
file: PropTypes.shape({
fileName: PropTypes.string,
fileUrl: PropTypes.string,
}).isRequired,
+ defaultOpen: PropTypes.bool,
// injected
// intl: intlShape.isRequired,
};
diff --git a/src/components/FilePreview/index.jsx b/src/components/FilePreview/index.jsx
index 38a32520..599d8641 100644
--- a/src/components/FilePreview/index.jsx
+++ b/src/components/FilePreview/index.jsx
@@ -3,12 +3,12 @@ import React from 'react';
import { useResponseData } from 'data/services/lms/hooks/selectors';
import { FileRenderer, isSupported } from './components';
-const FilePreview = () => {
+const FilePreview = ({ defaultCollapsePreview }) => {
const { uploadedFiles } = useResponseData();
return (
{uploadedFiles.filter(isSupported).map((file) => (
-
+
))}
);
diff --git a/src/components/FileUpload/index.jsx b/src/components/FileUpload/index.jsx
index 8aa3b12a..b3d49d16 100644
--- a/src/components/FileUpload/index.jsx
+++ b/src/components/FileUpload/index.jsx
@@ -24,6 +24,7 @@ const FileUpload = ({
uploadedFiles,
onFileUploaded,
onDeletedFile,
+ defaultCollapsePreview,
}) => {
const { formatMessage } = useIntl();
@@ -39,7 +40,7 @@ const FileUpload = ({
return (
File Upload
- {isReadOnly &&
}
+ {isReadOnly &&
}
{uploadedFiles.length > 0 && (
<>
Uploaded Files
@@ -91,6 +92,7 @@ FileUpload.defaultProps = {
uploadedFiles: [],
onFileUploaded: nullMethod,
onDeletedFile: nullMethod,
+ defaultCollapsePreview: false,
};
FileUpload.propTypes = {
isReadOnly: PropTypes.bool,
@@ -103,6 +105,7 @@ FileUpload.propTypes = {
),
onFileUploaded: PropTypes.func,
onDeletedFile: PropTypes.func,
+ defaultCollapsePreview: PropTypes.bool,
};
export default FileUpload;
diff --git a/src/components/InfoPopover/index.jsx b/src/components/InfoPopover/index.jsx
index cbc90895..a1cc459e 100644
--- a/src/components/InfoPopover/index.jsx
+++ b/src/components/InfoPopover/index.jsx
@@ -36,6 +36,7 @@ export const InfoPopover = ({ onClick, children }) => {
alt={formatMessage(messages.altText)}
iconAs={Icon}
onClick={onClick}
+ size="inline"
/>
);
diff --git a/src/components/Prompt/hooks.js b/src/components/Prompt/hooks.js
index 2cc77124..a688665f 100644
--- a/src/components/Prompt/hooks.js
+++ b/src/components/Prompt/hooks.js
@@ -1,7 +1,7 @@
import { useState } from 'react';
-const usePromptHooks = () => {
- const [open, setOpen] = useState(true);
+const usePromptHooks = ({ defaultOpen }) => {
+ const [open, setOpen] = useState(defaultOpen);
const toggleOpen = () => setOpen(!open);
diff --git a/src/components/Prompt/index.jsx b/src/components/Prompt/index.jsx
index 4e37a4ba..4931cba4 100644
--- a/src/components/Prompt/index.jsx
+++ b/src/components/Prompt/index.jsx
@@ -5,8 +5,8 @@ import { Collapsible } from '@edx/paragon';
import usePromptHooks from './hooks';
-const Prompt = ({ prompt }) => {
- const { open, toggleOpen } = usePromptHooks();
+const Prompt = ({ prompt, defaultOpen }) => {
+ const { open, toggleOpen } = usePromptHooks({ defaultOpen });
return (
@@ -14,7 +14,12 @@ const Prompt = ({ prompt }) => {
);
};
+Prompt.defaultProps = {
+ defaultOpen: true,
+};
+
Prompt.propTypes = {
+ defaultOpen: PropTypes.bool,
prompt: PropTypes.string.isRequired,
};
diff --git a/src/data/services/lms/constants.js b/src/data/services/lms/constants.js
index 50585d91..da3fb68c 100644
--- a/src/data/services/lms/constants.js
+++ b/src/data/services/lms/constants.js
@@ -50,7 +50,7 @@ export const routeSteps = StrictDict({
peer_assessment: stepNames.peer,
self_assessment: stepNames.self,
student_training: stepNames.studentTraining,
- my_grades: stepNames.done,
+ graded: stepNames.done,
});
export const stepRoutes = StrictDict(Object.keys(routeSteps).reduce(
diff --git a/src/data/services/lms/fakeData/constants.js b/src/data/services/lms/fakeData/constants.js
index 00dacbcb..d95b5852 100644
--- a/src/data/services/lms/fakeData/constants.js
+++ b/src/data/services/lms/fakeData/constants.js
@@ -7,7 +7,7 @@ export const viewKeys = StrictDict({
studentTraining: 'student_training',
self: 'self_assessment',
peer: 'peer_assessment',
- done: 'my_grades',
+ done: 'graded',
});
export const progressKeys = StrictDict({
diff --git a/src/data/services/lms/fakeData/pageData/assessments.js b/src/data/services/lms/fakeData/pageData/assessments.js
index abef9a96..c64c3713 100644
--- a/src/data/services/lms/fakeData/pageData/assessments.js
+++ b/src/data/services/lms/fakeData/pageData/assessments.js
@@ -2,36 +2,19 @@ import { stepNames } from 'data/services/lms/constants';
import { progressKeys } from '../constants';
export const createAssessmentState = ({
- options_selected = [],
- criterion_feedback,
+ assessment_criterions = [],
overall_feedback = '',
}) => ({
- options_selected,
- criterion_feedback,
+ assessment_criterions,
overall_feedback,
});
-export const emptySelections = {
- 'Criterion 1 name': null,
- 'Criterion 2 name': null,
- 'Criterion 3 name': null,
- 'Criterion 4 name': null,
-};
-export const filledSelections = {
- 'Criterion 1 name': 'Option 4 name',
- 'Criterion 2 name': 'Option 3 name',
- 'Criterion 3 name': 'Option 2 name',
- 'Criterion 4 name': 'Option 1 name',
-};
-
const gradedState = createAssessmentState({
- options_selected: filledSelections,
- criterion_feedback: {
- 'Criterion 1 name': 'feedback 1',
- 'Criterion 2 name': 'feedback 2',
- 'Criterion 3 name': 'feedback 3',
- 'Criterion 4 name': 'feedback 4',
- },
+ assessment_criterions: new Array(4).fill(0).map((_, i) => ({
+ feedback: `feedback ${i + 1}`,
+ // random 0-3
+ selectedOption: Math.floor(Math.random() * 4)
+ })),
overall_feedback: 'nice job',
});
@@ -40,6 +23,7 @@ export const getAssessmentState = ({ progressKey, stepConfig }) => {
return null;
}
const out = {};
+ out.effectiveAssessmentType = 'peer';
if (stepConfig.includes(stepNames.staff)) {
out.staff = {
stepScore: { earned: 10, possible: 10 },
@@ -49,7 +33,7 @@ export const getAssessmentState = ({ progressKey, stepConfig }) => {
if (stepConfig.includes(stepNames.peer)) {
out.peer = {
stepScore: { earned: 10, possible: 10 },
- assessment: [
+ assessments: [
gradedState,
gradedState,
gradedState,
@@ -59,13 +43,19 @@ export const getAssessmentState = ({ progressKey, stepConfig }) => {
};
out.peerUnweighted = {
stepScore: null,
- assessment: [
+ assessments: [
gradedState,
gradedState,
gradedState,
],
};
}
+ if (stepConfig.includes(stepNames.self)) {
+ out.self = {
+ stepScore: { earned: 10, possible: 10 },
+ assessment: gradedState,
+ };
+ }
return out;
};
diff --git a/src/data/services/lms/hooks/data.ts b/src/data/services/lms/hooks/data.ts
index 01d04d19..2fd964f5 100644
--- a/src/data/services/lms/hooks/data.ts
+++ b/src/data/services/lms/hooks/data.ts
@@ -1,9 +1,6 @@
import { useQuery, useMutation } from '@tanstack/react-query';
-import {
- useParams,
- useLocation,
-} from 'react-router-dom';
+import { useParams, useLocation } from 'react-router-dom';
import { camelCaseObject } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
@@ -25,17 +22,19 @@ export const useORAConfig = (): types.QueryData => {
({ data }) => camelCaseObject(data)
);
*/
- return Promise.resolve(camelCaseObject(fakeData.oraConfig.assessmentTinyMCE));
+ return Promise.resolve(
+ camelCaseObject(fakeData.oraConfig.assessmentTinyMCE)
+ );
},
});
-}
+};
export const usePageData = (): types.QueryData => {
const location = useLocation();
const { progressKey } = useParams();
const view = location.pathname.split('/')[1];
const pageDataUrl = usePageDataUrl(view);
-
+
return useQuery({
queryKey: [queryKeys.pageData],
queryFn: () => {
@@ -49,9 +48,10 @@ export const usePageData = (): types.QueryData => {
});
};
-export const useSubmitResponse = () => useMutation({
- mutationFn: (response) => {
- console.log({ submit: response });
- return Promise.resolve();
- },
-});
+export const useSubmitResponse = () =>
+ useMutation({
+ mutationFn: (response) => {
+ console.log({ submit: response });
+ return Promise.resolve();
+ },
+ });
diff --git a/src/data/services/lms/types/pageData.ts b/src/data/services/lms/types/pageData.ts
index fdc8d362..82dda1a1 100644
--- a/src/data/services/lms/types/pageData.ts
+++ b/src/data/services/lms/types/pageData.ts
@@ -76,30 +76,30 @@ export interface ResponseData {
// Assessments Data
export interface AssessmentData {
- optionsSelected: { [key: string]: string | null },
- criterionFeedback: { [key: string]: string },
+ assessmentCriterions: {
+ selectedOption: number | null,
+ feedback: string,
+ }[],
overallFeedback: string | null,
}
export interface AssessmentsData {
effectiveAssessmentType: 'self' | 'peer' | 'staff',
- assessments: {
- staff?: {
- stepScore: { earned: number, possible: number },
- assessment: AssessmentData,
- },
- peer?: {
- stepScore: { earned: number, possible: number },
- assessments: AssessmentData[],
- },
- peerUnweighted?: {
- stepScore: null,
- assessmenst: AssessmentData[],
- },
- self?: {
- stepScore: { earned: number, possible: number },
- assessment: AssessmentData,
- },
+ staff?: {
+ stepScore: { earned: number, possible: number },
+ assessment: AssessmentData,
+ },
+ peer: {
+ stepScore: { earned: number, possible: number },
+ assessments: AssessmentData[],
+ },
+ peerUnweighted?: {
+ stepScore: null,
+ assessments: AssessmentData[],
+ },
+ self?: {
+ stepScore: { earned: number, possible: number },
+ assessment: AssessmentData,
},
}
diff --git a/src/routes.ts b/src/routes.ts
index 0101274d..a5e09b2d 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -4,11 +4,13 @@ export default {
selfAssessmentEmbed: 'self_assessment/embedded/:courseId/:xblockId/:progressKey?',
studentTrainingEmbed: 'student_training/embedded/:courseId/:xblockId/:progressKey?',
submissionEmbed: 'submission/embedded/:courseId/:xblockId/:progressKey?',
+ gradedEmbed: 'graded/embedded/:courseId/:xblockId/:progressKey?',
rootEmbed: 'embedded/*',
xblock: 'xblock/:courseId/:xblockId/:progressKey?',
peerAssessment: 'peer_assessment/:courseId/:xblockId/:progressKey?',
selfAssessment: 'self_assessment/:courseId/:xblockId/:progressKey?',
studentTraining: 'student_training/:courseId/:xblockId/:progressKey?',
submission: 'submission/:courseId/:xblockId/:progressKey?',
+ graded: 'graded/:courseId/:xblockId/:progressKey?',
root: '/*',
};
diff --git a/src/views/GradeView/Content.jsx b/src/views/GradeView/Content.jsx
new file mode 100644
index 00000000..c7851ebb
--- /dev/null
+++ b/src/views/GradeView/Content.jsx
@@ -0,0 +1,46 @@
+import React from 'react';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import {
+ usePrompts,
+ useResponseData,
+} from 'data/services/lms/hooks/selectors';
+
+import FileUpload from 'components/FileUpload';
+import Prompt from 'components/Prompt';
+import TextResponse from 'components/TextResponse';
+import messages from './messages';
+
+const Content = () => {
+ const prompts = usePrompts();
+ const response = useResponseData();
+ const { formatMessage } = useIntl();
+ return (
+
+
{formatMessage(messages.aboutYourGrade)}
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
+ commodo consequat.
+
+
+ {
+ prompts.map((prompt, index) => (
+
+ ))
+ }
+
+
+
+ );
+};
+
+Content.defaultProps = {};
+Content.propTypes = {};
+
+export default Content;
diff --git a/src/views/GradeView/FinalGrade.jsx b/src/views/GradeView/FinalGrade.jsx
new file mode 100644
index 00000000..8b8bf5cc
--- /dev/null
+++ b/src/views/GradeView/FinalGrade.jsx
@@ -0,0 +1,80 @@
+import React from 'react';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import { useAssessmentsData } from 'data/services/lms/hooks/selectors';
+import messages from './messages';
+import InfoPopover from 'components/InfoPopover';
+import {
+ SingleAssessmentStep,
+ MultipleAssessmentStep,
+} from 'components/CollapsibleFeedback';
+
+const FinalGrade = () => {
+ const { formatMessage } = useIntl();
+ const { effectiveAssessmentType, ...steps } = useAssessmentsData();
+
+ const finalStepScore = steps[effectiveAssessmentType]?.stepScore;
+
+ let result = [];
+ Object.keys(steps).forEach((step) => {
+ const stepLabel = formatMessage(messages[`${step}StepLabel`]);
+ const StepComponent = ['staff', 'self'].includes(step)
+ ? SingleAssessmentStep
+ : MultipleAssessmentStep;
+ if (step === effectiveAssessmentType) {
+ result = [
+ ,
+ ...result,
+ ];
+ } else {
+ result.push(
+
+ );
+ }
+ });
+
+ const [finalGrade, ...rest] = result;
+
+ return (
+
+
+ {formatMessage(messages.yourFinalGrade, finalStepScore)}
+ {}}>
+
+ {effectiveAssessmentType === 'peer'
+ ? formatMessage(messages.peerAsFinalGradeInfo)
+ : formatMessage(messages.finalGradeInfo, {
+ step: effectiveAssessmentType,
+ })}
+
+
+
+ {finalGrade}
+
+
+ {formatMessage(messages.unweightedGrades)}
+ {}}>
+ {formatMessage(messages.unweightedGradesInfo)}
+
+
+ {rest}
+
+ );
+};
+
+FinalGrade.defaultProps = {};
+FinalGrade.propTypes = {};
+
+export default FinalGrade;
diff --git a/src/views/GradeView/index.jsx b/src/views/GradeView/index.jsx
new file mode 100644
index 00000000..a5516bfd
--- /dev/null
+++ b/src/views/GradeView/index.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { ActionRow, Col, Row } from '@edx/paragon';
+
+import { AssessmentContextProvider } from 'components/AssessmentContext';
+
+import FinalGrade from './FinalGrade';
+import Content from './Content';
+
+import './index.scss';
+
+const GradeView = ({}) => (
+
+
+
+);
+GradeView.defaultProps = {};
+GradeView.propTypes = {};
+
+export default GradeView;
diff --git a/src/views/GradeView/index.scss b/src/views/GradeView/index.scss
new file mode 100644
index 00000000..22fb8803
--- /dev/null
+++ b/src/views/GradeView/index.scss
@@ -0,0 +1,14 @@
+@import "@edx/paragon/scss/core/core";
+
+.grade-view-body {
+ max-width: $max-width-xl;
+ flex-wrap: nowrap !important;
+ gap: 1em;
+}
+
+@include media-breakpoint-down(md) {
+ .grade-view-body {
+ flex-wrap: wrap !important;
+ gap: 0;
+ }
+}
diff --git a/src/views/GradeView/messages.js b/src/views/GradeView/messages.js
new file mode 100644
index 00000000..300c6df1
--- /dev/null
+++ b/src/views/GradeView/messages.js
@@ -0,0 +1,56 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ aboutYourGrade: {
+ id: 'ora-grade-view.aboutYourGrade',
+ defaultMessage: 'About your grade: ',
+ description: 'About your grade',
+ },
+ yourFinalGrade: {
+ id: 'ora-grade-view.yourFinalGrade',
+ defaultMessage: 'Your final grade: {earned}/{possible}',
+ description: 'Your final grade',
+ },
+ finalGradeInfo: {
+ id: 'ora-grade-view.finalGradeInfo',
+ defaultMessage: 'Your grade is based on your {step} score for this problem. Other assessments don\'t count towards your final score.',
+ description: 'Final grade info',
+ },
+ peerAsFinalGradeInfo: {
+ id: 'ora-grade-view.peerAsFinalGradeInfo',
+ defaultMessage: 'Only the required number of peer grades will counted against your final grade. The others are shown, but are not included in your grade calculation',
+ description: 'Peer as final grade info',
+ },
+ unweightedGradesInfo: {
+ id: 'ora-grade-view.unweightedGradesInfo',
+ defaultMessage: 'These grades are given to your response. However, these are not used to compute your final grade.',
+ description: 'Unweighted grades info',
+ },
+ unweightedGrades: {
+ id: 'ora-grade-view.unweightedGrades',
+ defaultMessage: 'Unweighted Grades',
+ description: 'Unweighted grades',
+ },
+ selfStepLabel: {
+ id: 'ora-grade-view.selfStepLabel',
+ defaultMessage: 'Self',
+ description: 'Self step label',
+ },
+ peerStepLabel: {
+ id: 'ora-grade-view.peerStepLabel',
+ defaultMessage: 'Peer',
+ description: 'Peer step label',
+ },
+ staffStepLabel: {
+ id: 'ora-grade-view.staffStepLabel',
+ defaultMessage: 'Staff',
+ description: 'Staff step label',
+ },
+ peerUnweightedStepLabel: {
+ id: 'ora-grade-view.unweightedPeerStepLabel',
+ defaultMessage: 'Unweighted Peer',
+ description: 'Unweighted peer step label',
+ },
+});
+
+export default messages;