11import React , { Component } from 'react' ;
22import PropTypes from 'prop-types' ;
33import { FormattedMessage , injectIntl } from 'react-intl' ;
4-
5- import { Table , Modal } from 'react-bootstrap' ;
4+ import { Field , reduxForm } from 'redux-form' ;
5+ import { Container , Row , Col , Table , Modal } from 'react-bootstrap' ;
66
77import UsersNameContainer from '../../../containers/UsersNameContainer' ;
88import EditShadowAssignmentPointsForm , {
99 getPointsFormInitialValues ,
1010 transformPointsFormSubmitData ,
1111} from '../../forms/EditShadowAssignmentPointsForm' ;
1212import Box from '../../widgets/Box' ;
13+ import Callout from '../../widgets/Callout' ;
14+ import { TextField , NumericTextField , SimpleCheckboxField } from '../../forms/Fields' ;
15+ import SubmitButton from '../../forms/SubmitButton' ;
1316import DateTime from '../../widgets/DateTime' ;
1417import Button , { TheButtonGroup } from '../../widgets/TheButton' ;
1518import Confirm from '../../forms/Confirm' ;
16- import Icon , { EditIcon , DeleteIcon } from '../../icons' ;
19+ import Icon , { BanIcon , EditIcon , DeleteIcon , SaveIcon } from '../../icons' ;
1720import { createUserNameComparator } from '../../helpers/users' ;
1821import { arrayToObject , safeGet } from '../../../helpers/common' ;
1922import withLinks from '../../../helpers/withLinks' ;
2023
2124class ShadowAssignmentPointsTable extends Component {
22- state = { dialogStudentId : null , dialogPointsId : null } ;
25+ state = { dialogStudentId : null , dialogPointsId : null , multiAwardMode : false } ;
26+
27+ toggleMultiAwardMode = ( ) => {
28+ this . setState ( { multiAwardMode : ! this . state . multiAwardMode } ) ;
29+ } ;
2330
2431 openDialog = ( studentId , pointsId = null ) =>
2532 this . setState ( {
@@ -50,6 +57,14 @@ class ShadowAssignmentPointsTable extends Component {
5057 points,
5158 permissionHints,
5259 maxPoints,
60+ submitting,
61+ handleSubmit,
62+ onSubmit,
63+ dirty,
64+ submitFailed = false ,
65+ submitSucceeded = false ,
66+ invalid,
67+ warning,
5368 intl : { locale } ,
5469 links : { GROUP_USER_SOLUTIONS_URI_FACTORY } ,
5570 } = this . props ;
@@ -61,14 +76,82 @@ class ShadowAssignmentPointsTable extends Component {
6176 title = {
6277 < FormattedMessage id = "app.shadowAssignmentPointsTable.title" defaultMessage = "Shadow Assignment Points" />
6378 }
64- collapsable
6579 isOpen
6680 noPadding
67- unlimitedHeight >
81+ unlimitedHeight
82+ footer = {
83+ permissionHints . createPoints ? (
84+ this . state . multiAwardMode ? (
85+ < >
86+ < Container fluid >
87+ < Row >
88+ < Col >
89+ < NumericTextField
90+ name = "points"
91+ maxLength = { 6 }
92+ validateMin = { - 10000 }
93+ validateMax = { 10000 }
94+ label = {
95+ < FormattedMessage id = "app.editShadowAssignmentPointsForm.points" defaultMessage = "Points:" />
96+ }
97+ />
98+ </ Col >
99+
100+ < Col lg = { 9 } >
101+ < Field
102+ name = "note"
103+ component = { TextField }
104+ maxLength = { 1024 }
105+ label = { < FormattedMessage id = "app.editShadowAssignmentPointsForm.note" defaultMessage = "Note:" /> }
106+ />
107+ </ Col >
108+ </ Row >
109+ </ Container >
110+
111+ { warning && < Callout variant = "warning" > { warning } </ Callout > }
112+
113+ < div className = "text-center text-nowrap mb-1" >
114+ < TheButtonGroup >
115+ < SubmitButton
116+ id = "multi-assign-form"
117+ handleSubmit = { handleSubmit ( data => onSubmit ( data ) . then ( this . toggleMultiAwardMode ) ) }
118+ submitting = { submitting }
119+ dirty = { dirty }
120+ hasSucceeded = { submitSucceeded }
121+ hasFailed = { submitFailed }
122+ invalid = { invalid }
123+ defaultIcon = { < SaveIcon gapRight /> }
124+ messages = { {
125+ submit : < FormattedMessage id = "generic.save" defaultMessage = "Save" /> ,
126+ submitting : < FormattedMessage id = "generic.saving" defaultMessage = "Saving..." /> ,
127+ success : < FormattedMessage id = "generic.saved" defaultMessage = "Saved" /> ,
128+ } }
129+ />
130+ < Button variant = "danger" onClick = { this . toggleMultiAwardMode } >
131+ < BanIcon gapRight />
132+ < FormattedMessage id = "generic.cancel" defaultMessage = "Cancel" />
133+ </ Button >
134+ </ TheButtonGroup >
135+ </ div >
136+ </ >
137+ ) : (
138+ < div className = "text-center" >
139+ < Button variant = "primary" onClick = { this . toggleMultiAwardMode } >
140+ < Icon gapRight icon = { [ 'far' , 'check-square' ] } />
141+ < FormattedMessage
142+ id = "app.shadowAssignmentPointsTable.multiAwardButton"
143+ defaultMessage = "Award Points Collectively"
144+ />
145+ </ Button >
146+ </ div >
147+ )
148+ ) : null
149+ } >
68150 < >
69- < Table responsive hover >
151+ < Table responsive hover className = "mb-1" >
70152 < thead >
71153 < tr >
154+ { this . state . multiAwardMode && < th /> }
72155 < th >
73156 < FormattedMessage id = "app.shadowAssignmentPointsTable.user" defaultMessage = "User" />
74157 </ th >
@@ -91,6 +174,13 @@ class ShadowAssignmentPointsTable extends Component {
91174 const awardedAt = safeGet ( studentPoints , [ student . id , 'awardedAt' ] , null ) ;
92175 return (
93176 < tr key = { student . id } >
177+ { this . state . multiAwardMode && (
178+ < td className = "shrink-col" >
179+ { points === null && permissionHints . createPoints && (
180+ < Field name = { `students.${ student . id } ` } component = { SimpleCheckboxField } />
181+ ) }
182+ </ td >
183+ ) }
94184 < td className = "text-nowrap" >
95185 < UsersNameContainer
96186 userId = { student . id }
@@ -101,20 +191,18 @@ class ShadowAssignmentPointsTable extends Component {
101191 < td className = "text-center text-nowrap" > { points !== null ? points : < span > —</ span > } </ td >
102192 < td > { awardedAt && < DateTime unixts = { awardedAt } showRelative /> } </ td >
103193 < td > { safeGet ( studentPoints , [ student . id , 'note' ] , null ) } </ td >
104- { points === null ? (
105- < td className = "shrink-col text-nowrap text-right" >
106- { permissionHints . createPoints && (
194+ < td className = "shrink-col text-nowrap text-right" >
195+ { points === null ? (
196+ permissionHints . createPoints && (
107197 < Button variant = "success" onClick = { ( ) => this . openDialog ( student . id ) } size = "xs" >
108198 < Icon gapRight icon = { [ 'far' , 'star' ] } />
109199 < FormattedMessage
110200 id = "app.shadowAssignmentPointsTable.createPointsButton"
111201 defaultMessage = "Award Points"
112202 />
113203 </ Button >
114- ) }
115- </ td >
116- ) : (
117- < td className = "shrink-col text-nowrap text-right" >
204+ )
205+ ) : (
118206 < TheButtonGroup >
119207 { permissionHints . updatePoints && (
120208 < Button variant = "warning" onClick = { ( ) => this . openDialog ( student . id , pointsId ) } size = "xs" >
@@ -143,8 +231,8 @@ class ShadowAssignmentPointsTable extends Component {
143231 </ Confirm >
144232 ) }
145233 </ TheButtonGroup >
146- </ td >
147- ) }
234+ ) }
235+ </ td >
148236 </ tr >
149237 ) ;
150238 } ) }
@@ -187,8 +275,41 @@ ShadowAssignmentPointsTable.propTypes = {
187275 maxPoints : PropTypes . number . isRequired ,
188276 setPoints : PropTypes . func . isRequired ,
189277 removePoints : PropTypes . func . isRequired ,
278+ handleSubmit : PropTypes . func . isRequired ,
279+ onSubmit : PropTypes . func . isRequired ,
280+ submitFailed : PropTypes . bool ,
281+ dirty : PropTypes . bool ,
282+ submitSucceeded : PropTypes . bool ,
283+ submitting : PropTypes . bool ,
284+ invalid : PropTypes . bool ,
285+ warning : PropTypes . any ,
190286 intl : PropTypes . object . isRequired ,
191287 links : PropTypes . object ,
192288} ;
193289
194- export default withLinks ( injectIntl ( ShadowAssignmentPointsTable ) ) ;
290+ const warn = ( { points } , { maxPoints } ) => {
291+ const warnings = { } ;
292+
293+ if ( maxPoints > 0 ) {
294+ if ( points > maxPoints || points < 0 ) {
295+ warnings . _warning = (
296+ < FormattedMessage
297+ id = "app.editShadowAssignmentPointsForm.validation.pointsOutOfRange"
298+ defaultMessage = "Points are out of regular range. Regular score for this assignment is between 0 and {maxPoints}."
299+ values = { { maxPoints } }
300+ />
301+ ) ;
302+ }
303+ }
304+
305+ return warnings ;
306+ } ;
307+
308+ export default withLinks (
309+ reduxForm ( {
310+ form : 'multi-assign-form' ,
311+ enableReinitialize : true ,
312+ keepDirtyOnReinitialize : false ,
313+ warn,
314+ } ) ( injectIntl ( ShadowAssignmentPointsTable ) )
315+ ) ;
0 commit comments