GradingPeriodForm.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. /*
  2. * Copyright (C) 2016 - 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 PropTypes from 'prop-types'
  20. import ReactDOM from 'react-dom'
  21. import update from 'immutability-helper'
  22. import _ from 'underscore'
  23. import Button from 'instructure-ui/lib/components/Button'
  24. import I18n from 'i18n!external_tools'
  25. import DueDateCalendarPicker from 'jsx/due_dates/DueDateCalendarPicker'
  26. import accessibleDateFormat from 'jsx/shared/helpers/accessibleDateFormat'
  27. import numberHelper from 'jsx/shared/helpers/numberHelper'
  28. import round from 'compiled/util/round'
  29. const Types = PropTypes;
  30. function roundWeight (val) {
  31. const value = numberHelper.parse(val);
  32. return isNaN(value) ? null : round(value, 2);
  33. };
  34. function buildPeriod (attr) {
  35. return {
  36. id: attr.id,
  37. title: attr.title,
  38. weight: roundWeight(attr.weight),
  39. startDate: attr.startDate,
  40. endDate: attr.endDate,
  41. closeDate: attr.closeDate
  42. };
  43. };
  44. let GradingPeriodForm = React.createClass({
  45. propTypes: {
  46. period: Types.shape({
  47. id: Types.string.isRequired,
  48. title: Types.string.isRequired,
  49. weight: Types.number,
  50. startDate: Types.instanceOf(Date).isRequired,
  51. endDate: Types.instanceOf(Date).isRequired,
  52. closeDate: Types.instanceOf(Date)
  53. }),
  54. weighted: Types.bool.isRequired,
  55. disabled: Types.bool.isRequired,
  56. onSave: Types.func.isRequired,
  57. onCancel: Types.func.isRequired
  58. },
  59. getInitialState: function() {
  60. let period = buildPeriod(this.props.period || {});
  61. return {
  62. period: period,
  63. preserveCloseDate: this.hasDistinctCloseDate(period)
  64. };
  65. },
  66. componentDidMount: function() {
  67. this.hackTheDatepickers();
  68. this.refs.title.focus();
  69. },
  70. triggerSave: function() {
  71. if (this.props.onSave) {
  72. this.props.onSave(this.state.period);
  73. }
  74. },
  75. triggerCancel: function() {
  76. if (this.props.onCancel) {
  77. this.setState({period: buildPeriod({})}, this.props.onCancel);
  78. }
  79. },
  80. hasDistinctCloseDate: function ({ endDate, closeDate }) {
  81. return closeDate && !_.isEqual(endDate, closeDate);
  82. },
  83. mergePeriod: function (attr) {
  84. return update(this.state.period, {$merge: attr});
  85. },
  86. changeTitle: function (e) {
  87. const period = this.mergePeriod({title: e.target.value});
  88. this.setState({period});
  89. },
  90. changeWeight: function (e) {
  91. const period = this.mergePeriod({weight: roundWeight(e.target.value)});
  92. this.setState({period});
  93. },
  94. changeStartDate: function (date) {
  95. const period = this.mergePeriod({startDate: date});
  96. this.setState({period});
  97. },
  98. changeEndDate: function (date) {
  99. let attr = {endDate: date};
  100. if (!this.state.preserveCloseDate && !this.hasDistinctCloseDate(this.state.period)) {
  101. attr.closeDate = date;
  102. }
  103. const period = this.mergePeriod(attr);
  104. this.setState({period});
  105. },
  106. changeCloseDate: function (date) {
  107. const period = this.mergePeriod({closeDate: date});
  108. this.setState({period: period, preserveCloseDate: !!date});
  109. },
  110. hackTheDatepickers: function() {
  111. // This can be replaced when we have an extensible datepicker
  112. let $form = ReactDOM.findDOMNode(this);
  113. let $appends = $form.querySelectorAll('.input-append');
  114. $appends.forEach(function($el) {
  115. $el.classList.add('ic-Input-group');
  116. });
  117. let $dateFields = $form.querySelectorAll('.date_field');
  118. $dateFields.forEach(function($el) {
  119. $el.classList.remove('date_field');
  120. $el.classList.add('ic-Input');
  121. });
  122. let $suggests = $form.querySelectorAll('.datetime_suggest');
  123. $suggests.forEach(function($el) {
  124. if(ENV.CONTEXT_TIMEZONE === ENV.TIMEZONE) {
  125. $el.remove();
  126. } else {
  127. $el.innerHTML = $el.innerHTML.replace(/Course/, 'Account');
  128. }
  129. });
  130. let $buttons = $form.querySelectorAll('.ui-datepicker-trigger');
  131. $buttons.forEach(function($el) {
  132. $el.classList.remove('btn');
  133. $el.classList.add('Button');
  134. });
  135. },
  136. renderSaveAndCancelButtons: function() {
  137. return (
  138. <div className="ic-Form-actions below-line">
  139. <Button
  140. ref = "cancelButton"
  141. disabled = {this.props.disabled}
  142. onClick = {this.triggerCancel}
  143. >
  144. {I18n.t("Cancel")}
  145. </Button>
  146. &nbsp;
  147. <Button
  148. variant = "primary"
  149. ref = "saveButton"
  150. aria-label = {I18n.t("Save Grading Period")}
  151. disabled = {this.props.disabled}
  152. onClick = {this.triggerSave}
  153. >
  154. {I18n.t("Save")}
  155. </Button>
  156. </div>
  157. );
  158. },
  159. renderWeightInput: function () {
  160. if (!this.props.weighted) return null;
  161. return (
  162. <div className="ic-Form-control">
  163. <label className="ic-Label" htmlFor="weight">
  164. {I18n.t('Grading Period Weight')}
  165. </label>
  166. <div className="input-append">
  167. <input
  168. id="weight"
  169. ref={(ref) => { this.weightInput = ref }}
  170. type="text"
  171. className="span1"
  172. defaultValue={I18n.n(this.state.period.weight)}
  173. onChange={this.changeWeight}
  174. />
  175. <span className="add-on">%</span>
  176. </div>
  177. </div>
  178. );
  179. },
  180. render: function() {
  181. return (
  182. <div className='GradingPeriodForm'>
  183. <div className="grid-row">
  184. <div className="col-xs-12 col-lg-8">
  185. <div className="ic-Form-group ic-Form-group--horizontal">
  186. <div className="ic-Form-control">
  187. <label className="ic-Label" htmlFor="title">
  188. {I18n.t("Grading Period Title")}
  189. </label>
  190. <input
  191. id='title'
  192. ref='title'
  193. className='ic-Input'
  194. title={I18n.t('Grading Period Title')}
  195. defaultValue={this.state.period.title}
  196. onChange={this.changeTitle}
  197. type='text'
  198. />
  199. </div>
  200. <div className="ic-Form-control">
  201. <label id="start-date-label" htmlFor="start-date" className="ic-Label">
  202. {I18n.t("Start Date")}
  203. </label>
  204. <DueDateCalendarPicker
  205. disabled = {false}
  206. inputClasses = ''
  207. dateValue = {this.state.period.startDate}
  208. ref = "startDate"
  209. dateType = "due_at"
  210. handleUpdate = {this.changeStartDate}
  211. rowKey = "start-date"
  212. labelledBy = "start-date-label"
  213. isFancyMidnight = {false}
  214. />
  215. </div>
  216. <div className="ic-Form-control">
  217. <label id="end-date-label" htmlFor="end-date" className="ic-Label">
  218. {I18n.t("End Date")}
  219. </label>
  220. <DueDateCalendarPicker
  221. disabled = {false}
  222. inputClasses = ''
  223. dateValue = {this.state.period.endDate}
  224. ref = "endDate"
  225. dateType = "due_at"
  226. handleUpdate = {this.changeEndDate}
  227. rowKey = "end-date"
  228. labelledBy = "end-date-label"
  229. isFancyMidnight = {true}
  230. />
  231. </div>
  232. <div className="ic-Form-control">
  233. <label id="close-date-label" htmlFor="close-date" className="ic-Label">
  234. {I18n.t("Close Date")}
  235. </label>
  236. <DueDateCalendarPicker
  237. disabled = {false}
  238. inputClasses = ''
  239. dateValue = {this.state.period.closeDate}
  240. ref = "closeDate"
  241. dateType = "due_at"
  242. handleUpdate = {this.changeCloseDate}
  243. rowKey = "close-date"
  244. labelledBy = "close-date-label"
  245. isFancyMidnight = {true}
  246. />
  247. </div>
  248. {this.renderWeightInput()}
  249. </div>
  250. </div>
  251. </div>
  252. {this.renderSaveAndCancelButtons()}
  253. </div>
  254. );
  255. }
  256. });
  257. export default GradingPeriodForm