GradeInput.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /*
  2. * Copyright (C) 2017 - present Instructure, Inc.
  3. *
  4. * This file is part of Canvas.
  5. *
  6. * Canvas is free software: you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License as published by the Free
  8. * Software Foundation, version 3 of the License.
  9. *
  10. * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
  11. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License along
  16. * with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. import React from 'react';
  19. import { bool, func, number, oneOf, shape, string } from 'prop-types';
  20. import Select from 'instructure-ui/lib/components/Select';
  21. import TextInput from 'instructure-ui/lib/components/TextInput';
  22. import Typography from 'instructure-ui/lib/components/Typography';
  23. import I18n from 'i18n!gradebook';
  24. import GradeFormatHelper from 'jsx/gradebook/shared/helpers/GradeFormatHelper';
  25. function normalizeGrade (grade) {
  26. return GradeFormatHelper.formatGrade(grade, { defaultValue: null });
  27. }
  28. function normalizeSubmissionGrade (submission) {
  29. if (submission.excused) {
  30. return GradeFormatHelper.excused();
  31. }
  32. return normalizeGrade(submission.enteredGrade);
  33. }
  34. function gradeHasChanged (props, state) {
  35. return normalizeGrade(props.submission.enteredGrade) !== normalizeGrade(state.grade);
  36. }
  37. function assignmentLabel (assignment) {
  38. switch (assignment.gradingType) {
  39. case 'points': {
  40. const points = I18n.n(assignment.pointsPossible, { strip_insignificant_zeros: true, precision: 2 });
  41. return I18n.t('Grade out of %{points}', { points });
  42. }
  43. case 'percent': {
  44. const percentage = I18n.n(100, { percentage: true, precision: 2, strip_insignificant_zeros: true });
  45. return I18n.t('Grade out of %{percentage}', { percentage });
  46. }
  47. case 'letter_grade': {
  48. return I18n.t('Letter Grade');
  49. }
  50. case 'gpa_scale': {
  51. return I18n.t('Grade Point Average');
  52. }
  53. default: {
  54. return I18n.t('Grade');
  55. }
  56. }
  57. }
  58. function ExcusedSelect (props) {
  59. return (
  60. <Select {...props}>
  61. <option value="">{ I18n.t('Excused') }</option>
  62. </Select>
  63. );
  64. }
  65. function CompleteIncompleteSelect (props) {
  66. return (
  67. <Select {...props}>
  68. <option value="">{ I18n.t('Ungraded') }</option>
  69. <option value="complete">{ I18n.t('Complete') }</option>
  70. <option value="incomplete">{ I18n.t('Incomplete') }</option>
  71. </Select>
  72. );
  73. }
  74. export default class GradeInput extends React.Component {
  75. static propTypes = {
  76. assignment: shape({
  77. gradingType: oneOf(['gpa_scale', 'letter_grade', 'not_graded', 'pass_fail', 'points', 'percent']).isRequired,
  78. pointsPossible: number
  79. }).isRequired,
  80. disabled: bool,
  81. onSubmissionUpdate: func,
  82. submission: shape({
  83. enteredGrade: string,
  84. excused: bool.isRequired,
  85. id: string
  86. }).isRequired,
  87. submissionUpdating: bool
  88. };
  89. static defaultProps = {
  90. disabled: false,
  91. onSubmissionUpdate () {},
  92. submissionUpdating: false
  93. };
  94. constructor (props) {
  95. super(props);
  96. this.state = {
  97. excused: props.submission.excused,
  98. grade: normalizeSubmissionGrade(props.submission)
  99. };
  100. this.handleSelectChange = this.handleSelectChange.bind(this);
  101. this.handleTextChange = this.handleTextChange.bind(this);
  102. this.handleTextBlur = this.handleTextBlur.bind(this);
  103. this.handleGradeChange = this.handleGradeChange.bind(this);
  104. }
  105. componentWillReceiveProps (nextProps) {
  106. const submissionChanged = this.props.submission.id !== nextProps.submission.id;
  107. const submissionUpdated = this.props.submissionUpdating && !nextProps.submissionUpdating;
  108. if (submissionChanged || submissionUpdated) {
  109. this.setState({
  110. excused: nextProps.submission.excused,
  111. grade: normalizeSubmissionGrade(nextProps.submission)
  112. });
  113. }
  114. }
  115. handleTextBlur () {
  116. const enteredGrade = this.state.grade.trim();
  117. const excused = GradeFormatHelper.isExcused(enteredGrade);
  118. this.setState({
  119. excused,
  120. grade: excused ? GradeFormatHelper.excused() : enteredGrade
  121. }, () => {
  122. if (gradeHasChanged(this.props, this.state)) {
  123. this.handleGradeChange();
  124. }
  125. });
  126. }
  127. handleTextChange (event) {
  128. this.setState({
  129. grade: event.target.value
  130. });
  131. }
  132. handleSelectChange (event) {
  133. this.setState({
  134. grade: event.target.value
  135. }, this.handleGradeChange);
  136. }
  137. handleGradeChange () {
  138. const submission = { ...this.props.submission };
  139. if (this.state.excused) {
  140. submission.excused = true;
  141. submission.enteredGrade = null;
  142. } else {
  143. submission.excused = false;
  144. submission.enteredGrade = this.state.grade;
  145. }
  146. this.props.onSubmissionUpdate(submission);
  147. }
  148. render () {
  149. if (this.props.assignment.gradingType === 'not_graded') {
  150. return <Typography size="small" weight="bold">{ I18n.t('This assignment is not graded.') }</Typography>
  151. }
  152. const inputProps = {
  153. disabled: this.props.disabled || this.props.submissionUpdating || this.state.excused,
  154. id: 'grade-detail-tray--grade-input',
  155. label: assignmentLabel(this.props.assignment)
  156. };
  157. if (this.props.assignment.gradingType === 'pass_fail') {
  158. if (this.state.excused) {
  159. return <ExcusedSelect {...inputProps} />;
  160. }
  161. return (
  162. <CompleteIncompleteSelect
  163. {...inputProps}
  164. onChange={this.handleSelectChange}
  165. value={this.state.grade == null ? '' : this.state.grade}
  166. />
  167. );
  168. }
  169. return (
  170. <TextInput
  171. {...inputProps}
  172. inline
  173. onChange={this.handleTextChange}
  174. onBlur={this.handleTextBlur}
  175. value={this.state.grade == null ? '–' : this.state.grade}
  176. width="6em"
  177. />
  178. );
  179. }
  180. }