@@ -3,9 +3,10 @@ import PropTypes from 'prop-types'
33import { connect } from 'react-redux'
44import cn from 'classnames'
55import { PrimaryButton , OutlineButton } from '../../Buttons'
6- import { REVIEW_OPPORTUNITY_TYPES } from '../../../config/constants'
6+ import { REVIEW_OPPORTUNITY_TYPES , VALIDATION_VALUE_TYPE } from '../../../config/constants'
77import { loadScorecards , loadDefaultReviewers } from '../../../actions/challenges'
88import styles from './ChallengeReviewer-Field.module.scss'
9+ import { convertDollarToInteger , validateValue } from '../../../util/input-check'
910
1011class ChallengeReviewerField extends Component {
1112 constructor ( props ) {
@@ -69,11 +70,11 @@ class ChallengeReviewerField extends Component {
6970 scorecardId : ( defaultReviewer && defaultReviewer . scorecardId ) || '' ,
7071 isMemberReview : true ,
7172 memberReviewerCount : ( defaultReviewer && defaultReviewer . memberReviewerCount ) || 1 ,
72- phaseId : ( defaultReviewer && defaultReviewer . phaseId ) || ( firstPhase ? firstPhase . id : '' ) ,
73- basePayment : ( defaultReviewer && defaultReviewer . basePayment ) || 0 ,
73+ phaseId : ( defaultReviewer && defaultReviewer . phaseId ) || ( firstPhase ? ( firstPhase . id || firstPhase . phaseId ) : '' ) ,
74+ basePayment : ( defaultReviewer && defaultReviewer . basePayment ) || '0' ,
7475 incrementalPayment : ( defaultReviewer && defaultReviewer . incrementalPayment ) || 0 ,
7576 type : ( defaultReviewer && defaultReviewer . opportunityType ) || REVIEW_OPPORTUNITY_TYPES . REGULAR_REVIEW ,
76- isAIReviewer : false
77+ isAIReviewer : Boolean ( ( defaultReviewer && defaultReviewer . isAIReviewer ) || false )
7778 }
7879
7980 const updatedReviewers = currentReviewers . concat ( [ newReviewer ] )
@@ -98,7 +99,7 @@ class ChallengeReviewerField extends Component {
9899 }
99100
100101 findDefaultReviewer ( ) {
101- const { challenge, metadata } = this . props
102+ const { challenge, metadata = { } } = this . props
102103 const { defaultReviewers = [ ] } = metadata
103104
104105 if ( ! challenge || ! challenge . trackId || ! challenge . typeId ) {
@@ -113,6 +114,10 @@ class ChallengeReviewerField extends Component {
113114 validateReviewer ( reviewer ) {
114115 const errors = [ ]
115116
117+ if ( typeof reviewer . isAIReviewer !== 'boolean' ) {
118+ errors . push ( 'Reviewer type must be specified' )
119+ }
120+
116121 if ( ! reviewer . scorecardId ) {
117122 errors . push ( 'Scorecard is required' )
118123 }
@@ -121,11 +126,13 @@ class ChallengeReviewerField extends Component {
121126 errors . push ( 'Phase is required' )
122127 }
123128
124- if ( ! reviewer . isAIReviewer && ( ! reviewer . memberReviewerCount || reviewer . memberReviewerCount < 1 ) ) {
125- errors . push ( 'Number of reviewers must be at least 1' )
129+ const memberCount = parseInt ( reviewer . memberReviewerCount ) || 1
130+ if ( ! reviewer . isAIReviewer && ( memberCount < 1 || ! Number . isInteger ( memberCount ) ) ) {
131+ errors . push ( 'Number of reviewers must be a positive integer' )
126132 }
127133
128- if ( ! reviewer . isAIReviewer && ( ! reviewer . basePayment || reviewer . basePayment < 0 ) ) {
134+ const basePayment = convertDollarToInteger ( reviewer . basePayment , '' )
135+ if ( ! reviewer . isAIReviewer && ( basePayment < 0 ) ) {
129136 errors . push ( 'Base payment must be non-negative' )
130137 }
131138
@@ -140,14 +147,14 @@ class ChallengeReviewerField extends Component {
140147 }
141148
142149 renderReviewerForm ( reviewer , index ) {
143- const { challenge, metadata, readOnly = false } = this . props
150+ const { challenge, metadata = { } , readOnly = false } = this . props
144151 const { scorecards = [ ] } = metadata
145152 const validationErrors = this . validateReviewer ( reviewer )
146153
147154 return (
148155 < div key = { `reviewer-${ index } ` } className = { styles . reviewerForm } >
149156 < div className = { styles . reviewerHeader } >
150- < h4 > Reviewer { index + 1 } </ h4 >
157+ { index > 0 && < h4 > Reviewer { index + 1 } </ h4 > }
151158 { ! readOnly && (
152159 < OutlineButton
153160 text = 'Remove'
@@ -160,7 +167,7 @@ class ChallengeReviewerField extends Component {
160167 { validationErrors . length > 0 && (
161168 < div className = { styles . validationErrors } >
162169 { validationErrors . map ( ( error , i ) => (
163- < div key = { i } className = { styles . validationError } > { error } </ div >
170+ < div key = { `error- ${ index } - ${ i } - ${ error } ` } className = { styles . validationError } > { error } </ div >
164171 ) ) }
165172 </ div >
166173 ) }
@@ -185,12 +192,12 @@ class ChallengeReviewerField extends Component {
185192 updatedReviewers [ index ] = {
186193 scorecardId : currentReviewer . scorecardId ,
187194 isMemberReview : ! isAI ,
188- memberReviewerCount : currentReviewer . memberReviewerCount ,
195+ memberReviewerCount : currentReviewer . memberReviewerCount || 1 ,
189196 phaseId : currentReviewer . phaseId ,
190- basePayment : currentReviewer . basePayment ,
191- incrementalPayment : currentReviewer . incrementalPayment ,
197+ basePayment : currentReviewer . basePayment || '0' ,
198+ incrementalPayment : currentReviewer . incrementalPayment || 0 ,
192199 type : currentReviewer . type ,
193- isAIReviewer : isAI
200+ isAIReviewer : Boolean ( isAI )
194201 }
195202
196203 onUpdateReviewers ( { field : 'reviewers' , value : updatedReviewers } )
@@ -231,7 +238,9 @@ class ChallengeReviewerField extends Component {
231238 { readOnly ? (
232239 < span >
233240 { ( ( ) => {
234- const phase = challenge . phases && challenge . phases . find ( p => p . id === reviewer . phaseId )
241+ const phase = challenge . phases && challenge . phases . find ( p =>
242+ ( p . id === reviewer . phaseId ) || ( p . phaseId === reviewer . phaseId )
243+ )
235244 return phase ? ( phase . name || `Phase ${ phase . phaseId || phase . id } ` ) : 'Not selected'
236245 } ) ( ) }
237246 </ span >
@@ -242,12 +251,13 @@ class ChallengeReviewerField extends Component {
242251 >
243252 < option value = '' > Select Phase</ option >
244253 { challenge . phases && challenge . phases
245- . filter ( phase =>
246- phase . name &&
247- phase . name . toLowerCase ( ) . includes ( 'review' )
248- )
254+ . filter ( phase => {
255+ const isReviewPhase = phase . name && phase . name . toLowerCase ( ) . includes ( 'review' )
256+ const isCurrentlySelected = ( phase . id === reviewer . phaseId ) || ( phase . phaseId === reviewer . phaseId )
257+ return isReviewPhase || isCurrentlySelected
258+ } )
249259 . map ( phase => (
250- < option key = { phase . id } value = { phase . id } >
260+ < option key = { phase . id || phase . phaseId } value = { phase . phaseId || phase . id } >
251261 { phase . name || `Phase ${ phase . phaseId || phase . id } ` }
252262 </ option >
253263 ) ) }
@@ -267,22 +277,29 @@ class ChallengeReviewerField extends Component {
267277 type = 'number'
268278 min = '1'
269279 value = { reviewer . memberReviewerCount || 1 }
270- onChange = { ( e ) => this . updateReviewer ( index , 'memberReviewerCount' , parseInt ( e . target . value ) ) }
280+ onChange = { ( e ) => {
281+ const validatedValue = validateValue ( e . target . value , VALIDATION_VALUE_TYPE . INTEGER )
282+ const parsedValue = parseInt ( validatedValue ) || 1
283+ this . updateReviewer ( index , 'memberReviewerCount' , Math . max ( 1 , parsedValue ) )
284+ } }
271285 />
272286 ) }
273287 </ div >
274288
275289 < div className = { styles . formGroup } >
276290 < label > Base Payment ($):</ label >
277291 { readOnly ? (
278- < span > ${ reviewer . basePayment || 0 } </ span >
292+ < span > ${ reviewer . basePayment || '0' } </ span >
279293 ) : (
280294 < input
281295 type = 'number'
282296 min = '0'
283297 step = '0.01'
284- value = { reviewer . basePayment || 0 }
285- onChange = { ( e ) => this . updateReviewer ( index , 'basePayment' , parseFloat ( e . target . value ) ) }
298+ value = { reviewer . basePayment || '0' }
299+ onChange = { ( e ) => {
300+ const validatedValue = validateValue ( e . target . value , VALIDATION_VALUE_TYPE . INTEGER )
301+ this . updateReviewer ( index , 'basePayment' , validatedValue )
302+ } }
286303 />
287304 ) }
288305 </ div >
@@ -339,7 +356,7 @@ class ChallengeReviewerField extends Component {
339356 }
340357
341358 render ( ) {
342- const { challenge, metadata, isLoading, readOnly = false } = this . props
359+ const { challenge, metadata = { } , isLoading, readOnly = false } = this . props
343360 const { error } = this . state
344361 const { scorecards = [ ] , defaultReviewers = [ ] } = metadata
345362 const reviewers = challenge . reviewers || [ ]
@@ -384,7 +401,7 @@ class ChallengeReviewerField extends Component {
384401 </ div >
385402 ) }
386403
387- { ! readOnly && reviewers . length === 0 && (
404+ { ! readOnly && reviewers && reviewers . length === 0 && (
388405 < div className = { styles . noReviewers } >
389406 < p > No reviewers configured. Click "Add Reviewer" to get started.</ p >
390407 { this . findDefaultReviewer ( ) && (
@@ -400,34 +417,34 @@ class ChallengeReviewerField extends Component {
400417 </ div >
401418 ) }
402419
403- { readOnly && reviewers . length === 0 && (
420+ { readOnly && reviewers && reviewers . length === 0 && (
404421 < div className = { styles . noReviewers } >
405422 < p > No reviewers configured for this challenge.</ p >
406423 </ div >
407424 ) }
408425
409- { reviewers . map ( ( reviewer , index ) =>
426+ { reviewers && reviewers . map ( ( reviewer , index ) =>
410427 this . renderReviewerForm ( reviewer , index )
411428 ) }
412429
413- { reviewers . length > 0 && (
430+ { reviewers && reviewers . length > 0 && (
414431 < div className = { styles . summary } >
415432 < h4 > Review Summary</ h4 >
416433 < div className = { styles . summaryRow } >
417434 < span > Total Member Reviewers:</ span >
418- < span > { reviewers . filter ( r => ! r . isAIReviewer ) . reduce ( ( sum , r ) => sum + ( r . memberReviewerCount || 0 ) , 0 ) } </ span >
435+ < span > { reviewers . filter ( r => Boolean ( r . isAIReviewer ) === false ) . reduce ( ( sum , r ) => sum + ( parseInt ( r . memberReviewerCount ) || 0 ) , 0 ) } </ span >
419436 </ div >
420437 < div className = { styles . summaryRow } >
421438 < span > Total AI Reviewers:</ span >
422- < span > { reviewers . filter ( r => r . isAIReviewer ) . length } </ span >
439+ < span > { reviewers . filter ( r => Boolean ( r . isAIReviewer ) === true ) . length } </ span >
423440 </ div >
424441 < div className = { styles . summaryRow } >
425442 < span > Total Review Cost:</ span >
426- < span > ${ reviewers . filter ( r => ! r . isAIReviewer ) . reduce ( ( sum , r ) => {
427- const base = r . basePayment || 0
428- const count = r . memberReviewerCount || 1
443+ < span > ${ reviewers . filter ( r => Boolean ( r . isAIReviewer ) === false ) . reduce ( ( sum , r ) => {
444+ const base = convertDollarToInteger ( r . basePayment , '' )
445+ const count = parseInt ( r . memberReviewerCount ) || 1
429446 return sum + ( base * count )
430- } , 0 ) . toFixed ( 2 ) } </ span >
447+ } , 0 ) } </ span >
431448 </ div >
432449 </ div >
433450 ) }
@@ -462,7 +479,7 @@ ChallengeReviewerField.propTypes = {
462479}
463480
464481const mapStateToProps = ( state ) => ( {
465- metadata : state . challenges . metadata ,
482+ metadata : state . challenges . metadata || { } ,
466483 isLoading : state . challenges . isLoading
467484} )
468485
0 commit comments