Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lk/grade implementation #67

Merged
merged 11 commits into from
Oct 30, 2023
3 changes: 3 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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'),
<Route path={routes.rootEmbed} element={<ErrorPage message={formatMessage(messages.error404Message)} />} />,
];
const baseRoutes = [
Expand All @@ -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'),
<Route path={routes.root} element={<ErrorPage message={formatMessage(messages.error404Message)} />} />,
];

Expand Down
52 changes: 52 additions & 0 deletions src/components/CollapsibleFeedback/AssessmentCriterion.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Feedback
key={criterion.name}
criterionName={criterion.name}
criterionDescription={criterion.description}
selectedOption={option.name}
selectedPoints={option.points}
commentHeader={stepLabel}
commentBody={assessmentCriterion.feedback}
/>
);
})}
<Feedback
criterionName={formatMessage(messages.overallFeedback)}
commentHeader={stepLabel}
commentBody={overallFeedback}
/>
</>
);
};
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;
40 changes: 40 additions & 0 deletions src/components/CollapsibleFeedback/CollapsibleFeedback.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Collapsible
title={
<h3>
{formatMessage(messages.grade, { stepLabel })}
{stepScore && formatMessage(messages.gradePoints, stepScore)}
</h3>
}
open={open}
onToggle={toggle}
>
{children}
</Collapsible>
);
};
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;
78 changes: 78 additions & 0 deletions src/components/CollapsibleFeedback/Feedback.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className='mt-2'>
<div className='d-flex justify-content-between align-items-center'>
<h5 className='mb-0'>{criterionName}</h5>
{criterionDescription && (
<InfoPopover onClick={() => {}}>
<p>{criterionDescription}</p>
</InfoPopover>
)}
</div>
{selectedOption && (
<p>
{selectedOption} -- {selectedPoints} points
</p>
)}
</div>
<div className='bg-gray-100 p-3'>
<Collapsible.Advanced open={isExpanded} onToggle={toggle}>
<Collapsible.Trigger className='d-flex justify-content-between'>
<h5 className='mb-0'>{commentHeader} Comment</h5>
{isExpanded ? (
<div className='d-flex mb-0 small'>
<span>{formatMessage(messages.readLess)}</span>
<Icon src={ExpandLess} />
</div>
) : (
<div className='d-flex mb-0 small'>
<span>{formatMessage(messages.readMore)}</span>
<Icon src={ExpandMore} />
</div>
)}
</Collapsible.Trigger>
<Collapsible.Body className='pt-2'>
<p>{commentBody}</p>
</Collapsible.Body>
</Collapsible.Advanced>
</div>
</>
);
};
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;
48 changes: 48 additions & 0 deletions src/components/CollapsibleFeedback/MultipleAssessmentStep.jsx
Original file line number Diff line number Diff line change
@@ -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,
}) => (
<div className='my-2' key='peer'>
<CollapsibleFeedback stepLabel={stepLabel} stepScore={stepScore} defaultOpen={defaultOpen}>
{assessments?.map((assessment, index) => (
<Fragment key={index}>
<p className='mb-0'>
{stepLabel} {index + 1}:
</p>
<AssessmentCriterion {...assessment} stepLabel={stepLabel} />
<hr className='my-4' />
</Fragment>
))}
</CollapsibleFeedback>
</div>
);

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;
40 changes: 40 additions & 0 deletions src/components/CollapsibleFeedback/SingleAssessmentStep.jsx
Original file line number Diff line number Diff line change
@@ -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,
}) => (
<CollapsibleFeedback
stepLabel={stepLabel}
stepScore={stepScore}
defaultOpen={defaultOpen}
>
<AssessmentCriterion {...assessment} stepLabel={stepLabel} />
</CollapsibleFeedback>
);

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;
5 changes: 5 additions & 0 deletions src/components/CollapsibleFeedback/index.jsx
Original file line number Diff line number Diff line change
@@ -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';
36 changes: 36 additions & 0 deletions src/components/CollapsibleFeedback/messages.js
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import './FileCard.scss';
/**
* <FileCard />
*/
const FileCard = ({ file, children }) => (
const FileCard = ({ file, children, defaultOpen }) => (
<Card className="file-card" key={file.fileName}>
<Collapsible
className="file-collapsible"
defaultOpen
defaultOpen={defaultOpen}
title={<h3 className="file-card-title">{file.fileName}</h3>}
>
<div className="preview-panel">
Expand All @@ -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;
9 changes: 6 additions & 3 deletions src/components/FilePreview/components/FileRenderer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useRenderData } from './hooks';
/**
* <FileRenderer />
*/
export const FileRenderer = ({ file }) => {
export const FileRenderer = ({ file, defaultOpen }) => {
const { formatMessage } = useIntl();
const {
Renderer,
Expand All @@ -21,7 +21,7 @@ export const FileRenderer = ({ file }) => {
} = useRenderData({ file, formatMessage });

return (
<FileCard key={file.fileUrl} file={file}>
<FileCard defaultOpen={defaultOpen} file={file}>
{isLoading && <LoadingBanner />}
{errorStatus ? (
<ErrorBanner {...error} />
Expand All @@ -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,
};
Expand Down
Loading
Loading