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;