diff --git a/package-lock.json b/package-lock.json
index cdc083bf..81aa7fac 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,8 +10,8 @@
"license": "AGPL-3.0",
"dependencies": {
"@edx/brand": "npm:@edx/brand-edx.org@2.1.2",
- "@edx/frontend-component-footer": "12.4.0",
- "@edx/frontend-component-header": "4.7.1",
+ "@edx/frontend-component-footer": "12.5.0",
+ "@edx/frontend-component-header": "4.8.0",
"@edx/frontend-platform": "5.6.1",
"@edx/paragon": "^21.5.3",
"@edx/react-unit-test-utils": "1.7.0",
@@ -37,13 +37,14 @@
"react-dom": "^17.0.2",
"react-pdf": "^7.4.0",
"react-redux": "7.2.9",
- "react-router": "6.16.0",
- "react-router-dom": "6.16.0",
+ "react-router": "6.17.0",
+ "react-router-dom": "6.17.0",
"redux": "4.2.1",
"redux-devtools-extension": "^2.13.9",
"redux-logger": "^3.0.6",
"regenerator-runtime": "0.14.0",
- "tinymce": "5.10.7"
+ "tinymce": "5.10.8",
+ "uuid": "^9.0.1"
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.1",
@@ -3121,9 +3122,9 @@
}
},
"node_modules/@edx/frontend-component-footer": {
- "version": "12.4.0",
- "resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-12.4.0.tgz",
- "integrity": "sha512-DmAC8eTB4ARYlzBewzxWB3Afn9v8j9nSudqDd6LiXHUYDZ8Z7QPrMfmrmfDHllflVd22NvljmP2hp+WdtjkgMA==",
+ "version": "12.5.0",
+ "resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-12.5.0.tgz",
+ "integrity": "sha512-kICrEglLQyMtM3Rp0Hw2ewMsqRtBojNrNgbNbQuQTjBFAWu3N137+WBb1Hljko59kJ97iiY8cIIEit9CnM3YnA==",
"dependencies": {
"@edx/paragon": "^21.3.1",
"@fortawesome/fontawesome-svg-core": "6.4.2",
@@ -3134,7 +3135,7 @@
"lodash": "^4.17.21"
},
"peerDependencies": {
- "@edx/frontend-platform": "^4.0.0 || ^5.0.0",
+ "@edx/frontend-platform": "^4.0.0 || ^5.0.0 || ^6.0.0",
"prop-types": "^15.5.10",
"react": "^16.9.0 || ^17.0.0",
"react-dom": "^16.9.0 || ^17.0.0"
@@ -3198,11 +3199,11 @@
}
},
"node_modules/@edx/frontend-component-header": {
- "version": "4.7.1",
- "resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-4.7.1.tgz",
- "integrity": "sha512-Fs4KXXgLBLjI6fH+AxpCyS2ItoGAtbWQ6J1GqK9MeoUsEFHBzL4//fBTbU2HwCJSpn7W8p63CYtk2EOy3eY1Og==",
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-4.8.0.tgz",
+ "integrity": "sha512-1uzT4LbDgiAYw9+WhbX6r3NGcA5DgscW7SzxXiqSqBM2ND9ym411eczVrxFzc7XGr0eZkQTrsgNDX7xagunYsg==",
"dependencies": {
- "@edx/paragon": "21.3.1",
+ "@edx/paragon": "21.5.3",
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-brands-svg-icons": "6.4.2",
"@fortawesome/free-regular-svg-icons": "6.4.2",
@@ -3214,67 +3215,12 @@
"react-transition-group": "4.4.5"
},
"peerDependencies": {
- "@edx/frontend-platform": "^4.0.0 || ^5.0.0",
+ "@edx/frontend-platform": "^4.0.0 || ^5.0.0 || ^6.0.0",
"prop-types": "^15.5.10",
"react": "^16.9.0 || ^17.0.0",
"react-dom": "^16.9.0 || ^17.0.0"
}
},
- "node_modules/@edx/frontend-component-header/node_modules/@edx/paragon": {
- "version": "21.3.1",
- "resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-21.3.1.tgz",
- "integrity": "sha512-bXTUaOEmT8XLnDQzYS8QLMvWK5K2BN4jHlx25lO8N0XWRQeDiQTdbx8OrEbv8QOPTlrv0an5MZc+qjlleJFObg==",
- "dependencies": {
- "@fortawesome/fontawesome-svg-core": "^6.1.1",
- "@fortawesome/react-fontawesome": "^0.1.18",
- "@popperjs/core": "^2.11.4",
- "bootstrap": "^4.6.2",
- "chalk": "^4.1.2",
- "child_process": "^1.0.2",
- "classnames": "^2.3.1",
- "email-prop-type": "^3.0.0",
- "file-selector": "^0.6.0",
- "font-awesome": "^4.7.0",
- "glob": "^8.0.3",
- "inquirer": "^8.2.5",
- "lodash.uniqby": "^4.7.0",
- "mailto-link": "^2.0.0",
- "prop-types": "^15.8.1",
- "react-bootstrap": "^1.6.5",
- "react-colorful": "^5.6.1",
- "react-dropzone": "^14.2.1",
- "react-focus-on": "^3.5.4",
- "react-loading-skeleton": "^3.1.0",
- "react-popper": "^2.2.5",
- "react-proptype-conditional-require": "^1.0.4",
- "react-responsive": "^8.2.0",
- "react-table": "^7.7.0",
- "react-transition-group": "^4.4.2",
- "tabbable": "^5.3.3",
- "uncontrollable": "^7.2.1",
- "uuid": "^9.0.0"
- },
- "bin": {
- "paragon": "bin/paragon-scripts.js"
- },
- "peerDependencies": {
- "react": "^16.8.6 || ^17.0.0",
- "react-dom": "^16.8.6 || ^17.0.0",
- "react-intl": "^5.25.1 || ^6.4.0"
- }
- },
- "node_modules/@edx/frontend-component-header/node_modules/@edx/paragon/node_modules/@fortawesome/react-fontawesome": {
- "version": "0.1.19",
- "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz",
- "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==",
- "dependencies": {
- "prop-types": "^15.8.1"
- },
- "peerDependencies": {
- "@fortawesome/fontawesome-svg-core": "~1 || ~6",
- "react": ">=16.x"
- }
- },
"node_modules/@edx/frontend-component-header/node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
@@ -3332,43 +3278,6 @@
"node": ">=6"
}
},
- "node_modules/@edx/frontend-component-header/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@edx/frontend-component-header/node_modules/glob": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
- "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^5.0.1",
- "once": "^1.3.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@edx/frontend-component-header/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/@edx/frontend-platform": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-5.6.1.tgz",
@@ -3719,6 +3628,12 @@
"url": "https://opencollective.com/core-js"
}
},
+ "node_modules/@edx/react-unit-test-utils/node_modules/isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+ "peer": true
+ },
"node_modules/@edx/react-unit-test-utils/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
@@ -3730,6 +3645,59 @@
"node": ">=10"
}
},
+ "node_modules/@edx/react-unit-test-utils/node_modules/path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "peer": true,
+ "dependencies": {
+ "isarray": "0.0.1"
+ }
+ },
+ "node_modules/@edx/react-unit-test-utils/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "peer": true
+ },
+ "node_modules/@edx/react-unit-test-utils/node_modules/react-router": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
+ "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.13",
+ "history": "^4.9.0",
+ "hoist-non-react-statics": "^3.1.0",
+ "loose-envify": "^1.3.1",
+ "path-to-regexp": "^1.7.0",
+ "prop-types": "^15.6.2",
+ "react-is": "^16.6.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=15"
+ }
+ },
+ "node_modules/@edx/react-unit-test-utils/node_modules/react-router-dom": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz",
+ "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.13",
+ "history": "^4.9.0",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.6.2",
+ "react-router": "5.3.4",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=15"
+ }
+ },
"node_modules/@edx/reactifex": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@edx/reactifex/-/reactifex-2.2.0.tgz",
@@ -5377,9 +5345,9 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz",
- "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz",
+ "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==",
"engines": {
"node": ">=14.0.0"
}
@@ -19028,11 +18996,11 @@
}
},
"node_modules/react-router": {
- "version": "6.16.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz",
- "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==",
+ "version": "6.17.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz",
+ "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==",
"dependencies": {
- "@remix-run/router": "1.9.0"
+ "@remix-run/router": "1.10.0"
},
"engines": {
"node": ">=14.0.0"
@@ -19042,12 +19010,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.16.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz",
- "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==",
+ "version": "6.17.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz",
+ "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==",
"dependencies": {
- "@remix-run/router": "1.9.0",
- "react-router": "6.16.0"
+ "@remix-run/router": "1.10.0",
+ "react-router": "6.17.0"
},
"engines": {
"node": ">=14.0.0"
@@ -21691,9 +21659,9 @@
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"node_modules/tinymce": {
- "version": "5.10.7",
- "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-5.10.7.tgz",
- "integrity": "sha512-9UUjaO0R7FxcFo0oxnd1lMs7H+D0Eh+dDVo5hKbVe1a+VB0nit97vOqlinj+YwgoBDt6/DSCUoWqAYlLI8BLYA=="
+ "version": "5.10.8",
+ "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-5.10.8.tgz",
+ "integrity": "sha512-iyoo3VGMAJhLMDdblAefKvYgBRk9kQi58GTwAmoieqsyggGsKZWlQl/YY6nTILFHUCA1FhYu0HdmM5YYjs17UQ=="
},
"node_modules/tmp": {
"version": "0.0.33",
diff --git a/package.json b/package.json
index 2b67931f..8b93eda9 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,8 @@
"redux-devtools-extension": "^2.13.9",
"redux-logger": "^3.0.6",
"regenerator-runtime": "0.14.0",
- "tinymce": "5.10.8"
+ "tinymce": "5.10.8",
+ "uuid": "^9.0.1"
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.1",
diff --git a/src/components/CollapsibleFeedback/AssessmentCriterion.jsx b/src/components/Assessment/ReadonlyAssessment/AssessmentCriterion.jsx
similarity index 100%
rename from src/components/CollapsibleFeedback/AssessmentCriterion.jsx
rename to src/components/Assessment/ReadonlyAssessment/AssessmentCriterion.jsx
diff --git a/src/components/CollapsibleFeedback/CollapsibleFeedback.jsx b/src/components/Assessment/ReadonlyAssessment/CollapsibleAssessment.jsx
similarity index 65%
rename from src/components/CollapsibleFeedback/CollapsibleFeedback.jsx
rename to src/components/Assessment/ReadonlyAssessment/CollapsibleAssessment.jsx
index cd48b2ff..e9386113 100644
--- a/src/components/CollapsibleFeedback/CollapsibleFeedback.jsx
+++ b/src/components/Assessment/ReadonlyAssessment/CollapsibleAssessment.jsx
@@ -5,20 +5,27 @@ import { Collapsible } from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
-const CollapsibleFeedback = ({ children, stepScore, stepLabel, defaultOpen }) => {
+const CollapsibleAssessment = ({
+ children,
+ stepScore,
+ stepLabel,
+ defaultOpen,
+}) => {
const { formatMessage } = useIntl();
const [open, setOpen] = React.useState(defaultOpen);
-
const toggle = () => setOpen(!open);
return (
- {formatMessage(messages.grade, { stepLabel })}
+ {formatMessage(
+ stepScore ? messages.grade : messages.unweightedGrade,
+ { stepLabel },
+ )}
{stepScore && formatMessage(messages.gradePoints, stepScore)}
- }
+ )}
open={open}
onToggle={toggle}
>
@@ -26,15 +33,17 @@ const CollapsibleFeedback = ({ children, stepScore, stepLabel, defaultOpen }) =>
);
};
-CollapsibleFeedback.defaultProps = {};
-CollapsibleFeedback.propTypes = {
+CollapsibleAssessment.defaultProps = {
+ defaultOpen: false,
+};
+CollapsibleAssessment.propTypes = {
stepLabel: PropTypes.string.isRequired,
stepScore: PropTypes.shape({
earned: PropTypes.number,
possible: PropTypes.number,
- }),
+ }).isRequired,
children: PropTypes.node.isRequired,
defaultOpen: PropTypes.bool,
};
-export default CollapsibleFeedback;
+export default CollapsibleAssessment;
diff --git a/src/components/CollapsibleFeedback/Feedback.jsx b/src/components/Assessment/ReadonlyAssessment/Feedback.jsx
similarity index 71%
rename from src/components/CollapsibleFeedback/Feedback.jsx
rename to src/components/Assessment/ReadonlyAssessment/Feedback.jsx
index 5ac7137c..fd465ef8 100644
--- a/src/components/CollapsibleFeedback/Feedback.jsx
+++ b/src/components/Assessment/ReadonlyAssessment/Feedback.jsx
@@ -23,9 +23,9 @@ const Feedback = ({
return (
<>
-
-
-
{criterionName}
+
+
+
{criterionName}
{criterionDescription && (
{}}>
{criterionDescription}
@@ -33,28 +33,26 @@ const Feedback = ({
)}
{selectedOption && (
-
- {selectedOption} -- {selectedPoints} points
-
+
{selectedOption} -- {selectedPoints} points
)}
-
+
-
- {commentHeader} Comment
+
+ {commentHeader} Comment
{isExpanded ? (
-
+
{formatMessage(messages.readLess)}
) : (
-
+
{formatMessage(messages.readMore)}
)}
-
+
{commentBody}
@@ -68,9 +66,9 @@ Feedback.defaultProps = {
Feedback.propTypes = {
defaultOpen: PropTypes.bool,
criterionName: PropTypes.string.isRequired,
- criterionDescription: PropTypes.string,
- selectedOption: PropTypes.string,
- selectedPoints: PropTypes.number,
+ criterionDescription: PropTypes.string.isRequired,
+ selectedOption: PropTypes.string.isRequired,
+ selectedPoints: PropTypes.number.isRequired,
commentHeader: PropTypes.string.isRequired,
commentBody: PropTypes.string.isRequired,
};
diff --git a/src/components/Assessment/ReadonlyAssessment/hooks.js b/src/components/Assessment/ReadonlyAssessment/hooks.js
deleted file mode 100644
index e28ac224..00000000
--- a/src/components/Assessment/ReadonlyAssessment/hooks.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useRubricConfig } from 'data/services/lms/hooks/selectors';
-
-export const useReadonlyAssessmentData = ({ assessment }) => {
- const { criteria, feedbackConfig } = useRubricConfig();
-
- const criterionData = (criterion) => ({
- ...criterion,
- optionsValue: assessment.optionsSelected[criterion.name],
- optionsIsInvalid: false,
- feedbackValue: assessment.criterionFeedback[criterion.name],
- feedbackIsInvalid: false,
- });
-
- return {
- criteria: criteria.map(criterionData),
- // overall feedback
- overallFeedbackPrompt: feedbackConfig.defaultText,
- overallFeedback: assessment.overallFeedback,
- };
-};
-
-export default useReadonlyAssessmentData;
diff --git a/src/components/Assessment/ReadonlyAssessment/index.jsx b/src/components/Assessment/ReadonlyAssessment/index.jsx
index 888c0635..6b3a827a 100644
--- a/src/components/Assessment/ReadonlyAssessment/index.jsx
+++ b/src/components/Assessment/ReadonlyAssessment/index.jsx
@@ -1,57 +1,55 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { v4 as uuid } from 'uuid';
-import { Card } from '@edx/paragon';
-import { useIntl } from '@edx/frontend-platform/i18n';
+import AssessmentCriterion from './AssessmentCriterion';
+import CollapsibleAssessment from './CollapsibleAssessment';
-import CriterionContainer from 'components/CriterionContainer';
-import GradedCriterion from 'components/CriterionContainer/GradedCriterion';
-import { useReadonlyAssessmentData } from './hooks';
-import messages from '../messages';
-
-/**
- *
- */
-const ReadonlyAssessment = ({ assessment }) => {
+const ReadOnlyAssessment = (stepData) => {
const {
- criteria,
- overallFeedbackDisabled,
- } = useReadonlyAssessmentData({ assessment });
-
- const { formatMessage } = useIntl();
-
+ stepLabel,
+ step,
+ stepScore,
+ defaultOpen,
+ } = stepData;
+ const collapsibleProps = { stepLabel, stepScore, defaultOpen };
+ if (stepData.assessments) {
+ return (
+
+
+ {stepData.assessments.map((assessment, index) => (
+
+ {stepLabel} {index + 1}:
+
+
+
+ ))}
+
+
+ );
+ }
return (
-
-
- {formatMessage(messages.rubric)}
-
- {criteria.map((criterion) => (
-
- )}
- />
- ))}
- {!overallFeedbackDisabled && (
- {assessment.overallFeedback}
- )}
-
-
-
+
+
+
);
};
-
-ReadonlyAssessment.propTypes = {
- assessment: PropTypes.shape({
- optionsSelected: PropTypes.objectOf(PropTypes.string).isRequired,
- criterionFeedback: PropTypes.objectOf(PropTypes.string).isRequired,
- overallFeedback: PropTypes.string,
- }).isRequired,
+ReadOnlyAssessment.defaultProps = {
+ defaultOpen: false,
+ assessment: null,
+ assessments: null,
+ stepScore: null,
+};
+ReadOnlyAssessment.propTypes = {
+ stepLabel: PropTypes.string.isRequired,
+ step: PropTypes.string.isRequired,
+ stepScore: PropTypes.shape({
+ earned: PropTypes.number,
+ total: PropTypes.number,
+ }),
+ defaultOpen: PropTypes.bool,
+ assessment: PropTypes.shape({}),
+ assessments: PropTypes.arrayOf(PropTypes.shape({})),
};
-export default ReadonlyAssessment;
+export default ReadOnlyAssessment;
diff --git a/src/components/CollapsibleFeedback/messages.js b/src/components/Assessment/ReadonlyAssessment/messages.js
similarity index 80%
rename from src/components/CollapsibleFeedback/messages.js
rename to src/components/Assessment/ReadonlyAssessment/messages.js
index b6dadfc3..5a9a01bf 100644
--- a/src/components/CollapsibleFeedback/messages.js
+++ b/src/components/Assessment/ReadonlyAssessment/messages.js
@@ -11,10 +11,15 @@ const messages = defineMessages({
defaultMessage: 'Read less',
description: 'Read less button text',
},
+ unweightedGrade: {
+ id: 'ora-collapsible-comment.grade',
+ defaultMessage: '{stepLabel} Grade',
+ description: 'Unweighted grade group text',
+ },
grade: {
id: 'ora-collapsible-comment.grade',
- defaultMessage: '{stepLabel} Grade:',
- description: 'Grade button text',
+ defaultMessage: '{stepLabel} Grade: ',
+ description: 'Grade group text',
},
gradePoints: {
id: 'ora-collapsible-comment.gradePoints',
diff --git a/src/components/CollapsibleFeedback/MultipleAssessmentStep.jsx b/src/components/CollapsibleFeedback/MultipleAssessmentStep.jsx
deleted file mode 100644
index 9f5c7d04..00000000
--- a/src/components/CollapsibleFeedback/MultipleAssessmentStep.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-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
deleted file mode 100644
index ffb830ce..00000000
--- a/src/components/CollapsibleFeedback/SingleAssessmentStep.jsx
+++ /dev/null
@@ -1,40 +0,0 @@
-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
deleted file mode 100644
index d84328df..00000000
--- a/src/components/CollapsibleFeedback/index.jsx
+++ /dev/null
@@ -1,5 +0,0 @@
-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/ModalActions/actionConfigs.js b/src/components/ModalActions/actionConfigs.js
new file mode 100644
index 00000000..3ca3fc82
--- /dev/null
+++ b/src/components/ModalActions/actionConfigs.js
@@ -0,0 +1,20 @@
+
+const actions = {
+ submitResponse: {
+ onClick: submitResponseHandler,
+ state: submitResponseMutation.status,
+ messages: submitActionMessages,
+ },
+ saveAndFinishLater: {
+ onClick: saveResponse,
+ state: saveResponseStatus,
+ messages: saveActionMessages,
+ },
+ finishLater: {
+ message: saveActionMessages[MutationStatus.idle],
+ },
+ startTraining: {
+ onClick: saveResponse,
+ message: messages.startTraining,
+ },
+};
diff --git a/src/components/ModalActions/index.jsx b/src/components/ModalActions/index.jsx
index edb348e6..4a4ea3f3 100644
--- a/src/components/ModalActions/index.jsx
+++ b/src/components/ModalActions/index.jsx
@@ -4,42 +4,37 @@ import PropTypes from 'prop-types';
import { Button, StatefulButton } from '@edx/paragon';
import { MutationStatus } from 'data/services/lms/constants';
-import { useCloseModal } from 'hooks';
+import useModalActionConfig from './useModalActionConfig';
-const ModalActions = (props) => {
- // const { secondary } = props;
- const closeModal = useCloseModal();
- const { primary } = props;
- const secondary = props.secondary && {
- ...props.secondary,
- onClick: closeModal,
- };
- const className = 'w-100';
- const disabledStates = [MutationStatus.loading];
- console.log(props);
- const genButton = (variant, btnProps) => (btnProps.state
+const className = 'w-100';
+const disabledStates = [MutationStatus.loading];
+
+const ModalActions = ({ step, options }) => {
+ const actions = useModalActionConfig({ step, options });
+ console.log({ actions });
+ const { primary, secondary } = actions || {};
+
+ const actionButton = (variant, btnProps) => (btnProps.state
?
: );
return (
- {secondary && genButton('outline-primary', secondary)}
- {primary && genButton('primary', primary)}
+ {secondary && actionButton('outline-primary', secondary)}
+ {primary && actionButton('primary', primary)}
);
};
-const actionProps = PropTypes.shape({
- onClick: PropTypes.func,
- state: PropTypes.string,
- disabledStates: PropTypes.arrayOf(PropTypes.string),
- labels: PropTypes.objectOf(PropTypes.node),
-});
ModalActions.defaultProps = {
- secondary: null,
- primary: null,
+ options: {},
};
ModalActions.propTypes = {
- secondary: actionProps,
- primary: actionProps,
+ step: PropTypes.string.isRequired,
+ options: PropTypes.shape({
+ save: PropTypes.func,
+ saveStatus: PropTypes.string,
+ submit: PropTypes.func,
+ submitStatus: PropTypes.string,
+ }),
};
export default ModalActions;
diff --git a/src/components/ModalActions/messages.js b/src/components/ModalActions/messages.js
new file mode 100644
index 00000000..14a08cb6
--- /dev/null
+++ b/src/components/ModalActions/messages.js
@@ -0,0 +1,56 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ finishLater: {
+ id: 'ora-mfe.ModalActions.simpleAction.finishLater',
+ defaultMessage: 'Finish later',
+ description: 'Finish later (close) button text',
+ },
+ submitResponse: {
+ id: 'ora-mfe.ModalActions.submitResponse',
+ defaultMessage: 'Submit response',
+ description: 'Submit button text',
+ },
+ submittingResponse: {
+ id: 'ora-mfe.ModalActions.submittingResponse',
+ defaultMessage: 'Submitting response',
+ description: 'Submit button text while submitting',
+ },
+ responseSubmitted: {
+ id: 'ora-mfe.ModalActions.responseSubmitted',
+ defaultMessage: 'Response submitted',
+ description: 'Submit button text after successful submission',
+ },
+ savingResponse: {
+ id: 'ora-mfe.ModalActions.savingResponse',
+ defaultMessage: 'Saving response',
+ description: 'Save for later button text while saving',
+ },
+ startTraining: {
+ id: 'ora-mfe.ModalActions.startTraining',
+ defaultMessage: 'Begin practice grading',
+ description: 'Action button to begin studentTraining step',
+ },
+ startSelf: {
+ id: 'ora-mfe.ModalActions.startTraining',
+ defaultMessage: 'Begin self grading',
+ description: 'Action button to begin self assessment step',
+ },
+ startPeer: {
+ id: 'ora-mfe.ModalActions.startPeer',
+ defaultMessage: 'Begin peer grading',
+ description: 'Action button to begin peer assessment step',
+ },
+ viewGrades: {
+ id: 'ora-mfe.ModalActions.viewGrades',
+ defaultMessage: 'View your grades',
+ description: 'Action button to load Grades step',
+ },
+ exit: {
+ id: 'ora-mfe.ModalActions.exit',
+ defaultMessage: 'Exit',
+ description: 'Action button to exit Grades step',
+ },
+});
+
+export default messages;
diff --git a/src/components/ModalActions/useModalActionConfig.js b/src/components/ModalActions/useModalActionConfig.js
new file mode 100644
index 00000000..131fc060
--- /dev/null
+++ b/src/components/ModalActions/useModalActionConfig.js
@@ -0,0 +1,102 @@
+import React from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import { useCloseModal } from 'hooks';
+import {
+ stepNames,
+ stepRoutes,
+ stepStates,
+ MutationStatus,
+} from 'data/services/lms/constants';
+import {
+ useGlobalState,
+ useActiveStepName,
+} from 'data/services/lms/hooks/selectors';
+import messages from './messages';
+
+const useStartStepAction = (viewStep) => {
+ const { formatMessage } = useIntl();
+ const navigate = useNavigate();
+ const { courseId, xblockId } = useParams();
+
+ const stepName = useActiveStepName();
+
+ if (viewStep === stepNames.done || stepName === stepNames.submission) {
+ return null;
+ }
+
+ const onClick = () => navigate(`/${stepRoutes[stepName]}/${courseId}/${xblockId}`);
+
+ const startMessages = {
+ [stepNames.studentTraining]: messages.startTraining,
+ [stepNames.self]: messages.startSelf,
+ [stepNames.peer]: messages.startPeer,
+ [stepNames.done]: messages.viewGrades,
+ };
+ console.log({ stepName, startMessages });
+ return { children: formatMessage(startMessages[stepName]), onClick };
+};
+
+const useActiveSubmissionConfig = ({
+ options = {},
+ closeModal,
+ formatMessage,
+}) => {
+ const saveAndClose = React.useCallback(
+ () => (options.saveResponse ? options.saveResponse().then(closeModal) : null),
+ [options, closeModal],
+ );
+
+ if (!options) { return null; }
+
+ const { submit, submitStatus, saveResponseStatus } = options;
+
+ return {
+ primary: {
+ onClick: submit,
+ state: submitStatus,
+ labels: {
+ [MutationStatus.idle]: formatMessage(messages.submitResponse),
+ [MutationStatus.loading]: formatMessage(messages.submittingResponse),
+ },
+ },
+ secondary: {
+ onClick: saveAndClose,
+ state: saveResponseStatus,
+ labels: {
+ [MutationStatus.idle]: formatMessage(messages.finishLater),
+ [MutationStatus.loading]: formatMessage(messages.savingResponse),
+ },
+ },
+ };
+};
+
+const useModalActionConfig = ({ step, options }) => {
+ const globalState = useGlobalState({ step });
+ const closeModal = useCloseModal();
+ const startStepAction = useStartStepAction(step);
+ const { formatMessage } = useIntl();
+ const activeSubmissionConfig = useActiveSubmissionConfig({
+ options,
+ closeModal,
+ formatMessage,
+ });
+
+ if (globalState.stepState === stepStates.inProgress) {
+ return step === stepNames.submission ? activeSubmissionConfig : null;
+ }
+ if (globalState.activeStepState === stepStates.inProgress) {
+ return {
+ primary: startStepAction,
+ secondary: { children: formatMessage(messages.finishLater), onClick: closeModal },
+ };
+ }
+ if (step === stepNames.done) {
+ return { primary: { children: formatMessage(messages.exit), onClick: closeModal } };
+ }
+ return null;
+};
+
+export default useModalActionConfig;
diff --git a/src/components/TextResponseEditor/index.jsx b/src/components/TextResponseEditor/index.jsx
deleted file mode 100644
index 5da676a9..00000000
--- a/src/components/TextResponseEditor/index.jsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import TextEditor from 'components/TextResponseEditor/TextEditor';
-import RichTextEditor from 'components/TextResponseEditor/RichTextEditor';
-
-import './index.scss';
-
-const TextResponseEditor = ({ submissionConfig, value, onChange }) => {
- const { textResponseConfig } = submissionConfig;
- const { optional, enabled } = textResponseConfig;
- const props = {
- optional,
- disabled: !enabled,
- value,
- onChange,
- };
-
- return (
-
- {
- textResponseConfig?.editorType === 'text' ? :
- }
-
- );
-};
-
-TextResponseEditor.propTypes = {
- submissionConfig: PropTypes.shape({
- textResponseConfig: PropTypes.shape({
- optional: PropTypes.bool,
- enabled: PropTypes.bool,
- editorType: PropTypes.string,
- }),
- }).isRequired,
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
-};
-
-export default TextResponseEditor;
diff --git a/src/data/services/lms/fakeData/pageData/assessments.js b/src/data/services/lms/fakeData/pageData/assessments.js
index c64c3713..2449adf8 100644
--- a/src/data/services/lms/fakeData/pageData/assessments.js
+++ b/src/data/services/lms/fakeData/pageData/assessments.js
@@ -23,7 +23,7 @@ export const getAssessmentState = ({ progressKey, stepConfig }) => {
return null;
}
const out = {};
- out.effectiveAssessmentType = 'peer';
+ out.effectiveAssessmentType = 'staff';
if (stepConfig.includes(stepNames.staff)) {
out.staff = {
stepScore: { earned: 10, possible: 10 },
diff --git a/src/data/services/lms/hooks/actions.ts b/src/data/services/lms/hooks/actions.ts
index 404ac249..f40c24bb 100644
--- a/src/data/services/lms/hooks/actions.ts
+++ b/src/data/services/lms/hooks/actions.ts
@@ -51,17 +51,12 @@ export const useSubmitResponse = () => useCreateMutationAction(
export const useSaveResponse = () => useCreateMutationAction(
async (data: any, queryClient) => {
// TODO: save response for later
- await new Promise((resolve) => setTimeout(() => {
- fakeData.pageData.shapes.emptySubmission.submission.response = {
- uploaded_files: [
- ...data.response.uploadedFiles,
- ],
- text_responses: [
- ...data.response.textResponses,
- ],
- } as any;
- resolve(null);
- }, 1000));
+ await new Promise((resolve) => {
+ console.log({ save: data });
+ setTimeout(() => {
+ resolve(null);
+ }, 1000);
+ });
queryClient.invalidateQueries([queryKeys.pageData, false]);
return Promise.resolve(data);
diff --git a/src/data/services/lms/hooks/selectors/index.ts b/src/data/services/lms/hooks/selectors/index.ts
index e222ebfb..569027a6 100644
--- a/src/data/services/lms/hooks/selectors/index.ts
+++ b/src/data/services/lms/hooks/selectors/index.ts
@@ -83,12 +83,14 @@ export const useActiveStepConfig = () => {
export const useGlobalState = ({ step = null } = {}) => {
const activeStepName = selectors.useActiveStepName();
- const stepState = useStepState({ step: step || activeStepName });
+ const activeStepState = useStepState();
+ const stepState = useStepState({ step });
const lastStep = selectors.useLastStep();
const effectiveGrade = selectors.useEffectiveGrade();
const cancellationInfo = selectors.useCancellationInfo();
return {
activeStepName,
+ activeStepState,
cancellationInfo,
effectiveGrade,
lastStep,
@@ -96,6 +98,12 @@ export const useGlobalState = ({ step = null } = {}) => {
};
};
+export const useTextResponses = () => {
+ const prompts = selectors.usePrompts();
+ const response = selectors.useResponseData();
+ return response ? response.textResponses : prompts.map(() => '');
+};
+
export default StrictDict({
...selectors,
useStepState,
diff --git a/src/data/services/lms/hooks/selectors/oraConfig.ts b/src/data/services/lms/hooks/selectors/oraConfig.ts
index eacb87de..53e2ce87 100644
--- a/src/data/services/lms/hooks/selectors/oraConfig.ts
+++ b/src/data/services/lms/hooks/selectors/oraConfig.ts
@@ -39,13 +39,18 @@ export const useAssessmentStepOrder = (): string[] => useAssessmentStepConfig()?
export const useStepIndex = ({ step }): number => useAssessmentStepOrder().indexOf(step);
export const useLastStep = () => {
- const order = useAssessmentStepOrder().filter(step => step === stepNames.staff);
+ const order = useAssessmentStepOrder().filter(step => step !== stepNames.staff);
if (order.length) {
return order[order.length - 1];
}
return stepNames.submission;
};
+export const useEffectiveGradeStep = () => {
+ const order = useAssessmentStepOrder();
+ return order[order.length - 1];
+};
+
export const useRubricConfig = (): types.RubricConfig => useORAConfigData().rubricConfig;
export const useEmptyRubric = () => {
diff --git a/src/data/services/lms/messages.js b/src/data/services/lms/messages.js
new file mode 100644
index 00000000..d301ceb5
--- /dev/null
+++ b/src/data/services/lms/messages.js
@@ -0,0 +1,27 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+import { stepNames } from './constants';
+
+const messages = defineMessages({
+ [stepNames.studentTraining]: {
+ defaultMessage: 'Practice grading',
+ description: 'studentTraining label',
+ id: 'frontend-app-ora.lmsAPI.studentTrainingLabel',
+ },
+ [stepNames.self]: {
+ defaultMessage: 'Self assessment',
+ description: 'self label',
+ id: 'frontend-app-ora.lmsAPI.selfLabel',
+ },
+ [stepNames.peer]: {
+ defaultMessage: 'Peer assessment',
+ description: 'peer label',
+ id: 'frontend-app-ora.lmsAPI.peerLabel',
+ },
+ [stepNames.staff]: {
+ defaultMessage: 'Staff assessment',
+ description: 'staff label',
+ id: 'frontend-app-ora.lmsAPI.staffLabel',
+ },
+});
+
+export default messages;
diff --git a/src/hooks.js b/src/hooks.js
index 2e1c0f2d..1cc16309 100644
--- a/src/hooks.js
+++ b/src/hooks.js
@@ -7,8 +7,13 @@ export const useActiveView = () => useLocation().pathname.split('/')[1];
export const useIsEmbedded = () => useLocation().pathname.split('/')[2] === 'embedded';
export const useCloseModal = () => {
- const postMessage = (data) => window.parent.postMessage(data, document.referrer);
- return () => postMessage({ type: 'plugin.modal-close' });
+ if (document.referrer !== '') {
+ const postMessage = (data) => window.parent.postMessage(data, document.referrer);
+ return () => postMessage({ type: 'plugin.modal-close' });
+ }
+ return () => {
+ console.log("CLose Modal");
+ };
};
export const useOpenModal = () => {
diff --git a/src/views/GradeView/Content.jsx b/src/views/GradeView/Content.jsx
index c7851ebb..a0a6a61b 100644
--- a/src/views/GradeView/Content.jsx
+++ b/src/views/GradeView/Content.jsx
@@ -1,11 +1,15 @@
import React from 'react';
+import { v4 as uuid } from 'uuid';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
usePrompts,
useResponseData,
+ useEffectiveGradeStep,
} from 'data/services/lms/hooks/selectors';
+import { stepNames } from 'data/services/lms/constants';
+import apiMessages from 'data/services/lms/messages';
import FileUpload from 'components/FileUpload';
import Prompt from 'components/Prompt';
@@ -15,26 +19,31 @@ import messages from './messages';
const Content = () => {
const prompts = usePrompts();
const response = useResponseData();
+ const effectiveGradeStep = useEffectiveGradeStep();
const { formatMessage } = useIntl();
+ const stepLabel = formatMessage(apiMessages[effectiveGradeStep]);
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.
+ {formatMessage(messages.finalGradeInfo, { step: stepLabel })}
+ {effectiveGradeStep === stepNames.peer && (
+ <>
+
+ {formatMessage(messages.peerAsFinalGradeInfo)}
+ >
+ )}
{
prompts.map((prompt, index) => (
-
);
diff --git a/src/views/GradeView/FinalGrade.jsx b/src/views/GradeView/FinalGrade.jsx
index 8b8bf5cc..780c5a8c 100644
--- a/src/views/GradeView/FinalGrade.jsx
+++ b/src/views/GradeView/FinalGrade.jsx
@@ -3,49 +3,31 @@ 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';
+import ReadOnlyAssessment from 'components/Assessment/ReadonlyAssessment';
+import messages, { labelMessages } from './messages';
const FinalGrade = () => {
const { formatMessage } = useIntl();
- const { effectiveAssessmentType, ...steps } = useAssessmentsData();
+ const { effectiveAssessmentType, ...assessments } = 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 loadStepData = (step) => ({
+ ...assessments[step],
+ key: step,
+ step,
+ stepLabel: formatMessage(labelMessages[step]),
});
- const [finalGrade, ...rest] = result;
+ const effectiveAssessment = loadStepData(effectiveAssessmentType);
+ const finalStepScore = effectiveAssessment?.stepScore;
+
+ const extraGrades = Object.keys(assessments)
+ .filter(type => type !== effectiveAssessmentType)
+ .map(loadStepData);
+
+ const renderAssessment = (stepData, defaultOpen = false) => (
+
+ );
return (
@@ -55,21 +37,19 @@ const FinalGrade = () => {
{effectiveAssessmentType === 'peer'
? formatMessage(messages.peerAsFinalGradeInfo)
- : formatMessage(messages.finalGradeInfo, {
- step: effectiveAssessmentType,
- })}
+ : formatMessage(messages.finalGradeInfo, { step: effectiveAssessmentType })}
- {finalGrade}
-
+ {renderAssessment(effectiveAssessment, true)}
+
{formatMessage(messages.unweightedGrades)}
{}}>
{formatMessage(messages.unweightedGradesInfo)}
- {rest}
+ {extraGrades.map(assessment => renderAssessment(assessment, false))}
);
};
diff --git a/src/views/GradeView/index.jsx b/src/views/GradeView/index.jsx
index a5516bfd..59152685 100644
--- a/src/views/GradeView/index.jsx
+++ b/src/views/GradeView/index.jsx
@@ -1,26 +1,37 @@
import React from 'react';
-import PropTypes from 'prop-types';
-import { ActionRow, Col, Row } from '@edx/paragon';
+import { Layout, Row } from '@edx/paragon';
+
+import { stepNames } from 'data/services/lms/constants';
import { AssessmentContextProvider } from 'components/AssessmentContext';
+import ModalActions from 'components/ModalActions';
import FinalGrade from './FinalGrade';
import Content from './Content';
import './index.scss';
-const GradeView = ({}) => (
+const GradeView = () => (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
diff --git a/src/views/GradeView/messages.js b/src/views/GradeView/messages.js
index 300c6df1..90c2db83 100644
--- a/src/views/GradeView/messages.js
+++ b/src/views/GradeView/messages.js
@@ -53,4 +53,27 @@ const messages = defineMessages({
},
});
+export const labelMessages = {
+ self: {
+ id: 'ora-grade-view.selfStepLabel',
+ defaultMessage: 'Self',
+ description: 'Self step label',
+ },
+ peer: {
+ id: 'ora-grade-view.peerStepLabel',
+ defaultMessage: 'Peer',
+ description: 'Peer step label',
+ },
+ staff: {
+ id: 'ora-grade-view.staffStepLabel',
+ defaultMessage: 'Staff',
+ description: 'Staff step label',
+ },
+ peerUnweighted: {
+ id: 'ora-grade-view.unweightedPeerStepLabel',
+ defaultMessage: 'Unweighted Peer',
+ description: 'Unweighted peer step label',
+ },
+};
+
export default messages;
diff --git a/src/views/PeerAssessmentView/index.jsx b/src/views/PeerAssessmentView/index.jsx
index ca7feae2..a232a6e0 100644
--- a/src/views/PeerAssessmentView/index.jsx
+++ b/src/views/PeerAssessmentView/index.jsx
@@ -2,24 +2,43 @@ import React from 'react';
import { Button } from '@edx/paragon';
-import { useIsORAConfigLoaded } from 'data/services/lms/hooks/selectors';
+import {
+ useIsORAConfigLoaded,
+ usePrompts,
+ useResponseData,
+} from 'data/services/lms/hooks/selectors';
+import { stepNames } from 'data/services/lms/constants';
+import Prompt from 'components/Prompt';
+import TextResponse from 'components/TextResponse';
+import FileUpload from 'components/FileUpload';
import BaseAssessmentView from 'components/BaseAssessmentView';
import StatusAlert from 'components/StatusAlert';
+import ModalActions from 'components/ModalActions';
-import AssessmentContent from './Content';
-
-export const PeerAssessmentView = () => useIsORAConfigLoaded() && (
-
Cancel,
- Submit ,
- ]}
- submitAssessment={() => {}}
- >
-
-
-
-);
+export const PeerAssessmentView = () => {
+ const prompts = usePrompts();
+ const response = useResponseData();
+ if (!useIsORAConfigLoaded()) {
+ return null;
+ }
+ return (
+
{}}>
+
+
+ {React.Children.toArray(
+ prompts.map((prompt, index) => (
+
+ )),
+ )}
+
+
+
+
+ );
+};
export default PeerAssessmentView;
diff --git a/src/views/SelfAssessmentView/Content.jsx b/src/views/SelfAssessmentView/Content.jsx
deleted file mode 100644
index 73fe80a1..00000000
--- a/src/views/SelfAssessmentView/Content.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-
-import {
- usePrompts,
- useResponseData,
-} from 'data/services/lms/hooks/selectors';
-
-import Prompt from 'components/Prompt';
-import TextResponse from 'components/TextResponse';
-import FileUpload from 'components/FileUpload';
-
-const AssessmentContent = () => {
- const prompts = usePrompts();
- const response = useResponseData();
- return (
-
- {React.Children.toArray(
- prompts.map((prompt, index) => (
-
- )),
- )}
-
-
- );
-};
-
-export default AssessmentContent;
diff --git a/src/views/SelfAssessmentView/index.jsx b/src/views/SelfAssessmentView/index.jsx
index ab668e94..421765a2 100644
--- a/src/views/SelfAssessmentView/index.jsx
+++ b/src/views/SelfAssessmentView/index.jsx
@@ -1,26 +1,42 @@
import React from 'react';
import { Button } from '@edx/paragon';
-import { useIsORAConfigLoaded } from 'data/services/lms/hooks/selectors';
+import {
+ useIsORAConfigLoaded,
+ usePrompts,
+ useResponseData,
+} from 'data/services/lms/hooks/selectors';
+import { stepNames } from 'data/services/lms/constants';
+
+import FileUpload from 'components/FileUpload';
+import ModalActions from 'components/ModalActions';
+import Prompt from 'components/Prompt';
+import TextResponse from 'components/TextResponse';
import StatusAlert from 'components/StatusAlert';
import BaseAssessmentView from 'components/BaseAssessmentView';
-import AssessmentContent from './Content';
export const SelfAssessmentView = () => {
+ const prompts = usePrompts();
+ const response = useResponseData();
if (!useIsORAConfigLoaded()) {
return null;
}
return (
-
Cancel,
- Submit ,
- ]}
- submitAssessment={() => {}}
- >
+ {}}>
-
+
+ {React.Children.toArray(
+ prompts.map((prompt, index) => (
+
+ )),
+ )}
+
+
+
);
};
diff --git a/src/views/StudentTrainingView/Content.jsx b/src/views/StudentTrainingView/Content.jsx
deleted file mode 100644
index 73fe80a1..00000000
--- a/src/views/StudentTrainingView/Content.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-
-import {
- usePrompts,
- useResponseData,
-} from 'data/services/lms/hooks/selectors';
-
-import Prompt from 'components/Prompt';
-import TextResponse from 'components/TextResponse';
-import FileUpload from 'components/FileUpload';
-
-const AssessmentContent = () => {
- const prompts = usePrompts();
- const response = useResponseData();
- return (
-
- {React.Children.toArray(
- prompts.map((prompt, index) => (
-
- )),
- )}
-
-
- );
-};
-
-export default AssessmentContent;
diff --git a/src/views/StudentTrainingView/index.jsx b/src/views/StudentTrainingView/index.jsx
index 6c04b487..fdf66bae 100644
--- a/src/views/StudentTrainingView/index.jsx
+++ b/src/views/StudentTrainingView/index.jsx
@@ -1,25 +1,41 @@
import React from 'react';
-import { Button } from '@edx/paragon';
-
-import { useIsORAConfigLoaded } from 'data/services/lms/hooks/selectors';
+import {
+ useIsORAConfigLoaded,
+ usePrompts,
+ useResponseData,
+} from 'data/services/lms/hooks/selectors';
+import { stepNames } from 'data/services/lms/constants';
+import Prompt from 'components/Prompt';
+import TextResponse from 'components/TextResponse';
+import FileUpload from 'components/FileUpload';
+import ModalActions from 'components/ModalActions';
import BaseAssessmentView from 'components/BaseAssessmentView';
import StatusAlert from 'components/StatusAlert';
-import AssessmentContent from './Content';
-
-export const StudentTrainingView = () => useIsORAConfigLoaded() && (
- Cancel,
- Submit ,
- ]}
- submitAssessment={() => {}}
- >
-
-
-
-);
-
+export const StudentTrainingView = () => {
+ const prompts = usePrompts();
+ const response = useResponseData();
+ if (!useIsORAConfigLoaded()) {
+ return null;
+ }
+ return (
+ {}}>
+
+
+ {React.Children.toArray(
+ prompts.map((prompt, index) => (
+
+ )),
+ )}
+
+
+
+
+ );
+};
export default StudentTrainingView;
diff --git a/src/views/SubmissionView/Content.jsx b/src/views/SubmissionView/Content.jsx
deleted file mode 100644
index ef8573ed..00000000
--- a/src/views/SubmissionView/Content.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { Icon } from '@edx/paragon';
-import { CheckCircle } from '@edx/paragon/icons';
-import { useIntl } from '@edx/frontend-platform/i18n';
-
-import {
- usePrompts,
- useSubmissionConfig,
- useStepState,
-} from 'data/services/lms/hooks/selectors';
-import { stepNames, stepStates } from 'data/services/lms/constants';
-
-import FileUpload from 'components/FileUpload';
-import Instructions from 'components/Instructions';
-import Prompt from 'components/Prompt';
-import TextResponse from 'components/TextResponse';
-import TextResponseEditor from 'components/TextResponseEditor';
-import StatusAlert from 'components/StatusAlert';
-
-import messages from './messages';
-
-const SubmissionContent = ({
- textResponses,
- uploadedFiles,
-}) => {
- const submissionConfig = useSubmissionConfig();
-
- const stepState = useStepState({ step: stepNames.submission });
- const isReadOnly = stepState === stepStates.completed;
- const prompts = usePrompts();
- const { formatMessage } = useIntl();
- const createTextResponse = (index) => (isReadOnly
- ?
- : (
-
- ));
-
- return (
-
-
-
{formatMessage(messages.yourResponse)}
- {(!isReadOnly && textResponses.draftSaved) && (
-
-
- {formatMessage(messages.draftSaved)}
-
- )}
-
-
-
-
-
- {prompts.map((prompt, index) => (
- // eslint-disable-next-line react/no-array-index-key
-
-
- {createTextResponse(index)}
-
- ))}
-
-
-
- );
-};
-
-SubmissionContent.propTypes = {
- textResponses: PropTypes.shape({
- value: PropTypes.arrayOf(PropTypes.string).isRequired,
- onChange: PropTypes.func.isRequired,
- draftSaved: PropTypes.bool.isRequired,
- }).isRequired,
- uploadedFiles: PropTypes.shape({
- value: PropTypes.arrayOf(PropTypes.shape({
- fileDescription: PropTypes.string,
- fileName: PropTypes.string,
- fileSize: PropTypes.number,
- })),
- onDeletedFile: PropTypes.func.isRequired,
- onFileUploaded: PropTypes.func.isRequired,
- }).isRequired,
-};
-
-export default SubmissionContent;
diff --git a/src/views/SubmissionView/SubmissionPrompts.jsx b/src/views/SubmissionView/SubmissionPrompts.jsx
new file mode 100644
index 00000000..5bac6c32
--- /dev/null
+++ b/src/views/SubmissionView/SubmissionPrompts.jsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ usePrompts,
+ useSubmissionConfig,
+} from 'data/services/lms/hooks/selectors';
+
+import Prompt from 'components/Prompt';
+import TextResponse from 'components/TextResponse';
+import TextResponseEditor from './TextResponseEditor';
+
+const SubmissionPrompts = ({
+ textResponses,
+ onUpdateTextResponse,
+ isReadOnly,
+}) => {
+ const submissionConfig = useSubmissionConfig();
+
+ const response = (index) => {
+ if (!submissionConfig.textResponseConfig.enabled) {
+ return null;
+ }
+ return isReadOnly
+ ?
+ : (
+
+ );
+ };
+
+ return usePrompts().map((prompt, index) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ));
+};
+SubmissionPrompts.defaultProps = {
+ textResponses: null,
+};
+SubmissionPrompts.propTypes = {
+ textResponses: PropTypes.arrayOf(PropTypes.string),
+ onUpdateTextResponse: PropTypes.func.isRequired,
+};
+
+export default SubmissionPrompts;
diff --git a/src/components/TextResponseEditor/RichTextEditor.jsx b/src/views/SubmissionView/TextResponseEditor/RichTextEditor.jsx
similarity index 100%
rename from src/components/TextResponseEditor/RichTextEditor.jsx
rename to src/views/SubmissionView/TextResponseEditor/RichTextEditor.jsx
diff --git a/src/components/TextResponseEditor/RichTextEditor.test.jsx b/src/views/SubmissionView/TextResponseEditor/RichTextEditor.test.jsx
similarity index 100%
rename from src/components/TextResponseEditor/RichTextEditor.test.jsx
rename to src/views/SubmissionView/TextResponseEditor/RichTextEditor.test.jsx
diff --git a/src/components/TextResponseEditor/TextEditor.jsx b/src/views/SubmissionView/TextResponseEditor/TextEditor.jsx
similarity index 100%
rename from src/components/TextResponseEditor/TextEditor.jsx
rename to src/views/SubmissionView/TextResponseEditor/TextEditor.jsx
diff --git a/src/components/TextResponseEditor/TextEditor.test.jsx b/src/views/SubmissionView/TextResponseEditor/TextEditor.test.jsx
similarity index 100%
rename from src/components/TextResponseEditor/TextEditor.test.jsx
rename to src/views/SubmissionView/TextResponseEditor/TextEditor.test.jsx
diff --git a/src/components/TextResponseEditor/__snapshots__/RichTextEditor.test.jsx.snap b/src/views/SubmissionView/TextResponseEditor/__snapshots__/RichTextEditor.test.jsx.snap
similarity index 100%
rename from src/components/TextResponseEditor/__snapshots__/RichTextEditor.test.jsx.snap
rename to src/views/SubmissionView/TextResponseEditor/__snapshots__/RichTextEditor.test.jsx.snap
diff --git a/src/components/TextResponseEditor/__snapshots__/TextEditor.test.jsx.snap b/src/views/SubmissionView/TextResponseEditor/__snapshots__/TextEditor.test.jsx.snap
similarity index 100%
rename from src/components/TextResponseEditor/__snapshots__/TextEditor.test.jsx.snap
rename to src/views/SubmissionView/TextResponseEditor/__snapshots__/TextEditor.test.jsx.snap
diff --git a/src/components/TextResponseEditor/__snapshots__/index.test.jsx.snap b/src/views/SubmissionView/TextResponseEditor/__snapshots__/index.test.jsx.snap
similarity index 100%
rename from src/components/TextResponseEditor/__snapshots__/index.test.jsx.snap
rename to src/views/SubmissionView/TextResponseEditor/__snapshots__/index.test.jsx.snap
diff --git a/src/views/SubmissionView/TextResponseEditor/index.jsx b/src/views/SubmissionView/TextResponseEditor/index.jsx
new file mode 100644
index 00000000..55bcbd87
--- /dev/null
+++ b/src/views/SubmissionView/TextResponseEditor/index.jsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { useSubmissionConfig } from 'data/services/lms/hooks/selectors';
+
+import TextEditor from './TextEditor';
+import RichTextEditor from './RichTextEditor';
+
+import './index.scss';
+
+const TextResponseEditor = ({ value, onChange }) => {
+ const { textResponseConfig } = useSubmissionConfig();
+ const {
+ optional,
+ enabled,
+ editorType,
+ } = textResponseConfig || {};
+
+ if (!enabled) {
+ return null;
+ }
+
+ const EditorComponent = editorType === 'text' ? TextEditor : RichTextEditor;
+
+ return (
+
+
+
+ );
+};
+
+TextResponseEditor.propTypes = {
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+export default TextResponseEditor;
diff --git a/src/components/TextResponseEditor/index.scss b/src/views/SubmissionView/TextResponseEditor/index.scss
similarity index 100%
rename from src/components/TextResponseEditor/index.scss
rename to src/views/SubmissionView/TextResponseEditor/index.scss
diff --git a/src/components/TextResponseEditor/index.test.jsx b/src/views/SubmissionView/TextResponseEditor/index.test.jsx
similarity index 100%
rename from src/components/TextResponseEditor/index.test.jsx
rename to src/views/SubmissionView/TextResponseEditor/index.test.jsx
diff --git a/src/components/TextResponseEditor/messages.js b/src/views/SubmissionView/TextResponseEditor/messages.js
similarity index 100%
rename from src/components/TextResponseEditor/messages.js
rename to src/views/SubmissionView/TextResponseEditor/messages.js
diff --git a/src/views/SubmissionView/hooks.js b/src/views/SubmissionView/hooks.js
deleted file mode 100644
index 6da3ee39..00000000
--- a/src/views/SubmissionView/hooks.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import { useCallback, useEffect } from 'react';
-
-import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
-import { useIntl } from '@edx/frontend-platform/i18n';
-import {
- usePageData,
- usePrompts,
- useRubricConfig,
- useResponseData,
-} from 'data/services/lms/hooks/selectors';
-
-import {
- useSubmitResponse, useSaveResponse, useUploadFiles, useDeleteFile,
-} from 'data/services/lms/hooks/actions';
-import { MutationStatus } from 'data/services/lms/constants';
-import messages from './messages';
-
-export const stateKeys = StrictDict({
- textResponses: 'textResponses',
- uploadedFiles: 'uploadedFiles',
- isDirty: 'isDirty',
-});
-
-export const useTextResponses = () => {
- const response = useResponseData();
- const prompts = usePrompts();
-
- const [isDirty, setIsDirty] = useKeyedState(stateKeys.isDirty, false);
- const [value, setValue] = useKeyedState(
- stateKeys.textResponses,
- response ? response.textResponses : prompts.map(() => ''),
- );
-
- const saveResponseMutation = useSaveResponse();
-
- const saveResponseHandler = useCallback(() => {
- setIsDirty(false);
- saveResponseMutation.mutate({ textResponses: value });
- }, [setIsDirty, saveResponseMutation, value]);
-
- const onChange = useCallback((index) => (textResponse) => {
- setValue(oldResponses => {
- const out = [...oldResponses];
- out[index] = textResponse;
- return out;
- });
- setIsDirty(true);
- }, [setValue, setIsDirty]);
-
- return {
- formProps: {
- value,
- onChange,
- draftSaved: saveResponseMutation.status === MutationStatus.success && !isDirty,
- },
- saveResponse: {
- handler: saveResponseHandler,
- status: saveResponseMutation.status,
- },
- };
-};
-
-export const useUploadedFiles = () => {
- const deleteFileMutation = useDeleteFile();
- const uploadFilesMutation = useUploadFiles();
-
- const response = useResponseData();
-
- const [value, setValue] = useKeyedState(
- stateKeys.uploadedFiles,
- response ? response.uploadedFiles : [],
- );
-
- const onFileUploaded = useCallback(async (data) => {
- const { fileData, queryClient } = data;
- const uploadResponse = await uploadFilesMutation.mutateAsync(data);
- if (uploadResponse) {
- setValue((oldFiles) => [...oldFiles, uploadResponse.uploadedFiles[0]]);
- }
- }, [uploadFilesMutation, setValue]);
-
- const onDeletedFile = deleteFileMutation.mutateAsync;
-
- return { value, onFileUploaded, onDeletedFile };
-};
-
-const useSubmissionViewData = () => {
- const submitResponseMutation = useSubmitResponse();
- const textResponses = useTextResponses();
- const uploadedFiles = useUploadedFiles();
- const { showDuringResponse } = useRubricConfig();
- const { formatMessage } = useIntl();
-
- const submitResponseHandler = useCallback(() => {
- submitResponseMutation.mutate({
- textResponses: textResponses.formProps.value,
- uploadedFiles: uploadedFiles.value,
- });
- }, [submitResponseMutation, textResponses, uploadedFiles]);
-
- return {
- actionsProps: {
- primary: {
- onClick: submitResponseHandler,
- state: submitResponseMutation.status,
- disabledStates: [MutationStatus.loading],
- labels: {
- [MutationStatus.idle]: formatMessage(messages.submissionActionSubmit),
- [MutationStatus.loading]: formatMessage(messages.submissionActionSubmitting),
- [MutationStatus.success]: formatMessage(messages.submissionActionSubmitted),
- },
- },
- secondary: {
- onClick: textResponses.saveResponse.handler,
- state: textResponses.saveResponse.status,
- disabledStates: [MutationStatus.loading],
- labels: {
- [MutationStatus.idle]: formatMessage(messages.saveActionSave),
- [MutationStatus.loading]: formatMessage(messages.saveActionSaving),
- },
- },
- },
- formProps: {
- textResponses: textResponses.formProps,
- uploadedFiles,
- },
- showRubric: showDuringResponse,
- };
-};
-
-export default useSubmissionViewData;
diff --git a/src/views/SubmissionView/index.jsx b/src/views/SubmissionView/index.jsx
index 95cdd123..e61f9e28 100644
--- a/src/views/SubmissionView/index.jsx
+++ b/src/views/SubmissionView/index.jsx
@@ -1,24 +1,70 @@
import React from 'react';
-import { Col, Row } from '@edx/paragon';
+import { Col, Icon, Row } from '@edx/paragon';
+import { CheckCircle } from '@edx/paragon/icons';
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import { useStepState } from 'data/services/lms/hooks/selectors';
+import { stepNames, stepStates } from 'data/services/lms/constants';
import Rubric from 'components/Rubric';
-import Actions from 'components/ModalActions';
+import ModalActions from 'components/ModalActions';
+import FileUpload from 'components/FileUpload';
+import Instructions from 'components/Instructions';
+import StatusAlert from 'components/StatusAlert';
-import Content from './Content';
-import useSubmissionViewData from './hooks';
+import SubmissionPrompts from './SubmissionPrompts';
+import useSubmissionViewData from './useSubmissionViewData';
import './index.scss';
+import messages from './messages';
+
export const SubmissionView = () => {
- const { actionsProps, formProps, showRubric } = useSubmissionViewData();
+ const {
+ actionOptions,
+ showRubric,
+ textResponses,
+ onUpdateTextResponse,
+ isDraftSaved,
+ uploadedFiles,
+ onDeletedFile,
+ onFileUploaded,
+ } = useSubmissionViewData();
+
+ const stepState = useStepState({ step: stepNames.submission });
+ const isReadOnly = stepState === stepStates.completed;
+ const { formatMessage } = useIntl();
+
+ const draftIndicator = (!isReadOnly && isDraftSaved) && (
+
+
+ {formatMessage(messages.draftSaved)}
+
+ );
+
return (
-
-
+
+
+
{formatMessage(messages.yourResponse)}
+ {draftIndicator}
+
+
+
+
+
+
+
+
{showRubric && }
diff --git a/src/views/SubmissionView/messages.js b/src/views/SubmissionView/messages.js
index 23762b09..b28d4cef 100644
--- a/src/views/SubmissionView/messages.js
+++ b/src/views/SubmissionView/messages.js
@@ -1,4 +1,5 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
+import { MutationStatus } from 'data/services/lms/constants';
const messages = defineMessages({
yourResponse: {
@@ -24,31 +25,47 @@ const messages = defineMessages({
it.`,
description: 'Description for the instructions textarea',
},
- submissionActionSubmit: {
+ startTraining: {
+ id: 'ora-grading.SubmissionView.startTraining',
+ defaultMessage: 'Start practice grading',
+ description: 'Action button text for start action to Student Training step',
+ },
+});
+
+export const submitActionMessages = defineMessages({
+ [MutationStatus.idle]: {
id: 'ora-grading.SubmissionAction.submit',
defaultMessage: 'Submit response',
description: 'Submit button text',
},
- submissionActionSubmitting: {
+ [MutationStatus.loading]: {
id: 'ora-grading.SubmissionAction.submitting',
defaultMessage: 'Submitting response',
description: 'Submit button text while submitting',
},
- submissionActionSubmitted: {
+ [MutationStatus.success]: {
id: 'ora-grading.SubmissionAction.submitted',
defaultMessage: 'Response submitted',
description: 'Submit button text after successful submission',
},
- saveActionSave: {
+
+});
+
+export const saveActionMessages = defineMessages({
+ [MutationStatus.idle]: {
id: 'ora-grading.SaveAction.save',
defaultMessage: 'Finish later',
description: 'Save for later button text',
},
- saveActionSaving: {
+ [MutationStatus.loading]: {
id: 'ora-grading.SaveAction.saving',
defaultMessage: 'Saving response',
description: 'Save for later button text while saving',
},
});
-export default messages;
+export default {
+ ...messages,
+ ...submitActionMessages,
+ ...saveActionMessages,
+};
diff --git a/src/views/SubmissionView/useSubmissionViewData.js b/src/views/SubmissionView/useSubmissionViewData.js
new file mode 100644
index 00000000..cfcaf4e0
--- /dev/null
+++ b/src/views/SubmissionView/useSubmissionViewData.js
@@ -0,0 +1,48 @@
+import { useCallback } from 'react';
+
+import { useStepState, useRubricConfig } from 'data/services/lms/hooks/selectors';
+import { useSubmitResponse } from 'data/services/lms/hooks/actions';
+import { stepNames } from 'data/services/lms/constants';
+
+import useTextResponsesData from './useTextResponsesData';
+import useUploadedFilesData from './useUploadedFilesData';
+
+const useSubmissionViewData = () => {
+ const submitResponseMutation = useSubmitResponse();
+ const rubricConfig = useRubricConfig();
+
+ const {
+ textResponses,
+ onUpdateTextResponse,
+ isDraftSaved,
+ saveResponse,
+ saveResponseStatus,
+ } = useTextResponsesData();
+ const {
+ uploadedFiles,
+ onFileUploaded,
+ onDeletedFile,
+ } = useUploadedFilesData();
+
+ const submitResponseHandler = useCallback(() => {
+ submitResponseMutation.mutate({ textResponses, uploadedFiles });
+ }, [submitResponseMutation, textResponses, uploadedFiles]);
+
+ return {
+ actionOptions: {
+ saveResponse,
+ saveResponseStatus,
+ submit: submitResponseHandler,
+ submitStatus: submitResponseMutation.status,
+ },
+ textResponses,
+ onUpdateTextResponse,
+ isDraftSaved,
+ uploadedFiles,
+ onDeletedFile,
+ onFileUploaded,
+ showRubric: rubricConfig.showDuringResponse,
+ };
+};
+
+export default useSubmissionViewData;
diff --git a/src/views/SubmissionView/useTextResponsesData.js b/src/views/SubmissionView/useTextResponsesData.js
new file mode 100644
index 00000000..ba5b3d36
--- /dev/null
+++ b/src/views/SubmissionView/useTextResponsesData.js
@@ -0,0 +1,45 @@
+import { useCallback } from 'react';
+
+import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
+import { useTextResponses } from 'data/services/lms/hooks/selectors';
+
+import { useSaveResponse } from 'data/services/lms/hooks/actions';
+import { MutationStatus } from 'data/services/lms/constants';
+
+export const stateKeys = StrictDict({
+ textResponses: 'textResponses',
+ isDirty: 'isDirty',
+});
+
+const useTextResponsesData = () => {
+ const textResponses = useTextResponses();
+
+ const [isDirty, setIsDirty] = useKeyedState(stateKeys.isDirty, false);
+ const [value, setValue] = useKeyedState(stateKeys.textResponses, textResponses);
+
+ const saveResponseMutation = useSaveResponse();
+
+ const saveResponse = useCallback(() => {
+ setIsDirty(false);
+ return saveResponseMutation.mutateAsync({ textResponses: value });
+ }, [setIsDirty, saveResponseMutation, value]);
+
+ const onChange = useCallback((index) => (textResponse) => {
+ setValue(oldResponses => {
+ const out = [...oldResponses];
+ out[index] = textResponse;
+ return out;
+ });
+ setIsDirty(true);
+ }, [setValue, setIsDirty]);
+
+ return {
+ textResponses: value,
+ onUpdateTextResponse: onChange,
+ isDraftSaved: saveResponseMutation.status === MutationStatus.success && !isDirty,
+ saveResponse,
+ saveResponseStatus: saveResponseMutation.status,
+ };
+};
+
+export default useTextResponsesData;
diff --git a/src/views/SubmissionView/useUploadedFilesData.js b/src/views/SubmissionView/useUploadedFilesData.js
new file mode 100644
index 00000000..4aaf4484
--- /dev/null
+++ b/src/views/SubmissionView/useUploadedFilesData.js
@@ -0,0 +1,40 @@
+import { useCallback } from 'react';
+
+import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
+import { useResponseData } from 'data/services/lms/hooks/selectors';
+
+import {
+ useUploadFiles, useDeleteFile,
+} from 'data/services/lms/hooks/actions';
+
+export const stateKeys = StrictDict({ uploadedFiles: 'uploadedFiles' });
+
+const useUploadedFilesData = () => {
+ const deleteFileMutation = useDeleteFile();
+ const uploadFilesMutation = useUploadFiles();
+
+ const response = useResponseData();
+
+ const [value, setValue] = useKeyedState(
+ stateKeys.uploadedFiles,
+ response ? response.uploadedFiles : [],
+ );
+
+ const onFileUploaded = useCallback(async (data) => {
+ // const { fileData, queryClient } = data;
+ const uploadResponse = await uploadFilesMutation.mutateAsync(data);
+ if (uploadResponse) {
+ setValue((oldFiles) => [...oldFiles, uploadResponse.uploadedFiles[0]]);
+ }
+ }, [uploadFilesMutation, setValue]);
+
+ const onDeletedFile = deleteFileMutation.mutateAsync;
+
+ return {
+ uploadedFiles: value,
+ onFileUploaded,
+ onDeletedFile,
+ };
+};
+
+export default useUploadedFilesData;