Skip to content

Commit ca279a8

Browse files
committed
Show Reviewer section on Challenge view page
1 parent 866d9c8 commit ca279a8

File tree

3 files changed

+167
-103
lines changed

3 files changed

+167
-103
lines changed

src/components/ChallengeEditor/ChallengeReviewer-Field/index.js

Lines changed: 165 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,21 @@ class ChallengeReviewerField extends Component {
140140
}
141141

142142
renderReviewerForm (reviewer, index) {
143-
const { challenge, metadata } = this.props
143+
const { challenge, metadata, readOnly = false } = this.props
144144
const { scorecards = [] } = metadata
145145
const validationErrors = this.validateReviewer(reviewer)
146146

147147
return (
148148
<div key={`reviewer-${index}`} className={styles.reviewerForm}>
149149
<div className={styles.reviewerHeader}>
150150
<h4>Reviewer {index + 1}</h4>
151-
<OutlineButton
152-
text='Remove'
153-
type='danger'
154-
onClick={() => this.removeReviewer(index)}
155-
/>
151+
{!readOnly && (
152+
<OutlineButton
153+
text='Remove'
154+
type='danger'
155+
onClick={() => this.removeReviewer(index)}
156+
/>
157+
)}
156158
</div>
157159

158160
{validationErrors.length > 0 && (
@@ -166,129 +168,178 @@ class ChallengeReviewerField extends Component {
166168
<div className={styles.formRow}>
167169
<div className={styles.formGroup}>
168170
<label>Reviewer Type:</label>
169-
<select
170-
value={reviewer.isAIReviewer ? 'ai' : 'member'}
171-
onChange={(e) => {
172-
const isAI = e.target.value === 'ai'
173-
const { challenge, onUpdateReviewers } = this.props
174-
const currentReviewers = challenge.reviewers || []
175-
const updatedReviewers = currentReviewers.slice()
176-
177-
// Update both fields atomically to ensure XOR constraint is satisfied
178-
// Maintain correct field order as expected by API schema
179-
const currentReviewer = updatedReviewers[index]
180-
updatedReviewers[index] = {
181-
scorecardId: currentReviewer.scorecardId,
182-
isMemberReview: !isAI,
183-
memberReviewerCount: currentReviewer.memberReviewerCount,
184-
phaseId: currentReviewer.phaseId,
185-
basePayment: currentReviewer.basePayment,
186-
incrementalPayment: currentReviewer.incrementalPayment,
187-
type: currentReviewer.type,
188-
isAIReviewer: isAI
189-
}
190-
191-
onUpdateReviewers({ field: 'reviewers', value: updatedReviewers })
192-
}}
193-
>
194-
<option value='member'>Member Reviewer</option>
195-
<option value='ai'>AI Reviewer</option>
196-
</select>
171+
{readOnly ? (
172+
<span>{reviewer.isAIReviewer ? 'AI Reviewer' : 'Member Reviewer'}</span>
173+
) : (
174+
<select
175+
value={reviewer.isAIReviewer ? 'ai' : 'member'}
176+
onChange={(e) => {
177+
const isAI = e.target.value === 'ai'
178+
const { challenge, onUpdateReviewers } = this.props
179+
const currentReviewers = challenge.reviewers || []
180+
const updatedReviewers = currentReviewers.slice()
181+
182+
// Update both fields atomically to ensure XOR constraint is satisfied
183+
// Maintain correct field order as expected by API schema
184+
const currentReviewer = updatedReviewers[index]
185+
updatedReviewers[index] = {
186+
scorecardId: currentReviewer.scorecardId,
187+
isMemberReview: !isAI,
188+
memberReviewerCount: currentReviewer.memberReviewerCount,
189+
phaseId: currentReviewer.phaseId,
190+
basePayment: currentReviewer.basePayment,
191+
incrementalPayment: currentReviewer.incrementalPayment,
192+
type: currentReviewer.type,
193+
isAIReviewer: isAI
194+
}
195+
196+
onUpdateReviewers({ field: 'reviewers', value: updatedReviewers })
197+
}}
198+
>
199+
<option value='member'>Member Reviewer</option>
200+
<option value='ai'>AI Reviewer</option>
201+
</select>
202+
)}
197203
</div>
198204

199205
<div className={styles.formGroup}>
200206
<label>Scorecard:</label>
201-
<select
202-
value={reviewer.scorecardId}
203-
onChange={(e) => this.updateReviewer(index, 'scorecardId', e.target.value)}
204-
>
205-
<option value=''>Select Scorecard</option>
206-
{scorecards.map(scorecard => (
207-
<option key={scorecard.id} value={scorecard.id}>
208-
{scorecard.name} - {scorecard.type} ({scorecard.challengeTrack}) v{scorecard.version}
209-
</option>
210-
))}
211-
</select>
207+
{readOnly ? (
208+
<span>
209+
{(() => {
210+
const scorecard = scorecards.find(s => s.id === reviewer.scorecardId)
211+
return scorecard ? `${scorecard.name} - ${scorecard.type} (${scorecard.challengeTrack}) v${scorecard.version}` : 'Not selected'
212+
})()}
213+
</span>
214+
) : (
215+
<select
216+
value={reviewer.scorecardId}
217+
onChange={(e) => this.updateReviewer(index, 'scorecardId', e.target.value)}
218+
>
219+
<option value=''>Select Scorecard</option>
220+
{scorecards.map(scorecard => (
221+
<option key={scorecard.id} value={scorecard.id}>
222+
{scorecard.name} - {scorecard.type} ({scorecard.challengeTrack}) v{scorecard.version}
223+
</option>
224+
))}
225+
</select>
226+
)}
212227
</div>
213228

214229
<div className={styles.formGroup}>
215230
<label>Phase:</label>
216-
<select
217-
value={reviewer.phaseId}
218-
onChange={(e) => this.updateReviewer(index, 'phaseId', e.target.value)}
219-
>
220-
<option value=''>Select Phase</option>
221-
{challenge.phases && challenge.phases
222-
.filter(phase =>
223-
phase.name &&
224-
phase.name.toLowerCase().includes('review')
225-
)
226-
.map(phase => (
227-
<option key={phase.id} value={phase.id}>
228-
{phase.name || `Phase ${phase.phaseId || phase.id}`}
229-
</option>
230-
))}
231-
</select>
231+
{readOnly ? (
232+
<span>
233+
{(() => {
234+
const phase = challenge.phases && challenge.phases.find(p => p.id === reviewer.phaseId)
235+
return phase ? (phase.name || `Phase ${phase.phaseId || phase.id}`) : 'Not selected'
236+
})()}
237+
</span>
238+
) : (
239+
<select
240+
value={reviewer.phaseId}
241+
onChange={(e) => this.updateReviewer(index, 'phaseId', e.target.value)}
242+
>
243+
<option value=''>Select Phase</option>
244+
{challenge.phases && challenge.phases
245+
.filter(phase =>
246+
phase.name &&
247+
phase.name.toLowerCase().includes('review')
248+
)
249+
.map(phase => (
250+
<option key={phase.id} value={phase.id}>
251+
{phase.name || `Phase ${phase.phaseId || phase.id}`}
252+
</option>
253+
))}
254+
</select>
255+
)}
232256
</div>
233257
</div>
234258

235259
{!reviewer.isAIReviewer && (
236260
<div className={styles.formRow}>
237261
<div className={styles.formGroup}>
238262
<label>Number of Reviewers:</label>
239-
<input
240-
type='number'
241-
min='1'
242-
value={reviewer.memberReviewerCount || 1}
243-
onChange={(e) => this.updateReviewer(index, 'memberReviewerCount', parseInt(e.target.value))}
244-
/>
263+
{readOnly ? (
264+
<span>{reviewer.memberReviewerCount || 1}</span>
265+
) : (
266+
<input
267+
type='number'
268+
min='1'
269+
value={reviewer.memberReviewerCount || 1}
270+
onChange={(e) => this.updateReviewer(index, 'memberReviewerCount', parseInt(e.target.value))}
271+
/>
272+
)}
245273
</div>
246274

247275
<div className={styles.formGroup}>
248276
<label>Base Payment ($):</label>
249-
<input
250-
type='number'
251-
min='0'
252-
step='0.01'
253-
value={reviewer.basePayment || 0}
254-
onChange={(e) => this.updateReviewer(index, 'basePayment', parseFloat(e.target.value))}
255-
/>
277+
{readOnly ? (
278+
<span>${reviewer.basePayment || 0}</span>
279+
) : (
280+
<input
281+
type='number'
282+
min='0'
283+
step='0.01'
284+
value={reviewer.basePayment || 0}
285+
onChange={(e) => this.updateReviewer(index, 'basePayment', parseFloat(e.target.value))}
286+
/>
287+
)}
256288
</div>
257289

258290
<div className={styles.formGroup}>
259291
<label>Incremental Payment ($):</label>
260-
<input
261-
type='number'
262-
min='0'
263-
step='0.01'
264-
value={reviewer.incrementalPayment || 0}
265-
onChange={(e) => this.updateReviewer(index, 'incrementalPayment', parseFloat(e.target.value))}
266-
/>
292+
{readOnly ? (
293+
<span>${reviewer.incrementalPayment || 0}</span>
294+
) : (
295+
<input
296+
type='number'
297+
min='0'
298+
step='0.01'
299+
value={reviewer.incrementalPayment || 0}
300+
onChange={(e) => this.updateReviewer(index, 'incrementalPayment', parseFloat(e.target.value))}
301+
/>
302+
)}
267303
</div>
268304
</div>
269305
)}
270306

271307
<div className={styles.formRow}>
272308
<div className={styles.formGroup}>
273309
<label>Review Type:</label>
274-
<select
275-
value={reviewer.type || REVIEW_OPPORTUNITY_TYPES.REGULAR_REVIEW}
276-
onChange={(e) => this.updateReviewer(index, 'type', e.target.value)}
277-
>
278-
<option value={REVIEW_OPPORTUNITY_TYPES.REGULAR_REVIEW}>Regular Review</option>
279-
<option value={REVIEW_OPPORTUNITY_TYPES.COMPONENT_DEV_REVIEW}>Component Dev Review</option>
280-
<option value={REVIEW_OPPORTUNITY_TYPES.SPEC_REVIEW}>Spec Review</option>
281-
<option value={REVIEW_OPPORTUNITY_TYPES.ITERATIVE_REVIEW}>Iterative Review</option>
282-
<option value={REVIEW_OPPORTUNITY_TYPES.SCENARIOS_REVIEW}>Scenarios Review</option>
283-
</select>
310+
{readOnly ? (
311+
<span>
312+
{(() => {
313+
const typeMap = {
314+
[REVIEW_OPPORTUNITY_TYPES.REGULAR_REVIEW]: 'Regular Review',
315+
[REVIEW_OPPORTUNITY_TYPES.COMPONENT_DEV_REVIEW]: 'Component Dev Review',
316+
[REVIEW_OPPORTUNITY_TYPES.SPEC_REVIEW]: 'Spec Review',
317+
[REVIEW_OPPORTUNITY_TYPES.ITERATIVE_REVIEW]: 'Iterative Review',
318+
[REVIEW_OPPORTUNITY_TYPES.SCENARIOS_REVIEW]: 'Scenarios Review'
319+
}
320+
return typeMap[reviewer.type] || 'Regular Review'
321+
})()}
322+
</span>
323+
) : (
324+
<select
325+
value={reviewer.type || REVIEW_OPPORTUNITY_TYPES.REGULAR_REVIEW}
326+
onChange={(e) => this.updateReviewer(index, 'type', e.target.value)}
327+
>
328+
<option value={REVIEW_OPPORTUNITY_TYPES.REGULAR_REVIEW}>Regular Review</option>
329+
<option value={REVIEW_OPPORTUNITY_TYPES.COMPONENT_DEV_REVIEW}>Component Dev Review</option>
330+
<option value={REVIEW_OPPORTUNITY_TYPES.SPEC_REVIEW}>Spec Review</option>
331+
<option value={REVIEW_OPPORTUNITY_TYPES.ITERATIVE_REVIEW}>Iterative Review</option>
332+
<option value={REVIEW_OPPORTUNITY_TYPES.SCENARIOS_REVIEW}>Scenarios Review</option>
333+
</select>
334+
)}
284335
</div>
285336
</div>
286337
</div>
287338
)
288339
}
289340

290341
render () {
291-
const { challenge, metadata, isLoading } = this.props
342+
const { challenge, metadata, isLoading, readOnly = false } = this.props
292343
const { error } = this.state
293344
const { scorecards = [], defaultReviewers = [] } = metadata
294345
const reviewers = challenge.reviewers || []
@@ -327,11 +378,13 @@ class ChallengeReviewerField extends Component {
327378
<label>Review Configuration :</label>
328379
</div>
329380
<div className={cn(styles.field, styles.col2)}>
330-
<div className={styles.description}>
331-
Configure how this challenge will be reviewed. You can add multiple reviewers including AI and member reviewers.
332-
</div>
381+
{!readOnly && (
382+
<div className={styles.description}>
383+
Configure how this challenge will be reviewed. You can add multiple reviewers including AI and member reviewers.
384+
</div>
385+
)}
333386

334-
{reviewers.length === 0 && (
387+
{!readOnly && reviewers.length === 0 && (
335388
<div className={styles.noReviewers}>
336389
<p>No reviewers configured. Click "Add Reviewer" to get started.</p>
337390
{this.findDefaultReviewer() && (
@@ -347,6 +400,12 @@ class ChallengeReviewerField extends Component {
347400
</div>
348401
)}
349402

403+
{readOnly && reviewers.length === 0 && (
404+
<div className={styles.noReviewers}>
405+
<p>No reviewers configured for this challenge.</p>
406+
</div>
407+
)}
408+
350409
{reviewers.map((reviewer, index) =>
351410
this.renderReviewerForm(reviewer, index)
352411
)}
@@ -373,13 +432,15 @@ class ChallengeReviewerField extends Component {
373432
</div>
374433
)}
375434

376-
<div className={styles.addButton}>
377-
<PrimaryButton
378-
text='Add Reviewer'
379-
type='info'
380-
onClick={this.addReviewer}
381-
/>
382-
</div>
435+
{!readOnly && (
436+
<div className={styles.addButton}>
437+
<PrimaryButton
438+
text='Add Reviewer'
439+
type='info'
440+
onClick={this.addReviewer}
441+
/>
442+
</div>
443+
)}
383444
</div>
384445
</div>
385446
</>
@@ -395,6 +456,7 @@ ChallengeReviewerField.propTypes = {
395456
defaultReviewers: PropTypes.array
396457
}),
397458
isLoading: PropTypes.bool,
459+
readOnly: PropTypes.bool,
398460
loadScorecards: PropTypes.func.isRequired,
399461
loadDefaultReviewers: PropTypes.func.isRequired
400462
}

src/components/ChallengeEditor/ChallengeView/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ const ChallengeView = ({
241241
<TextEditorField
242242
challenge={challenge}
243243
readOnly
244+
showReviewerField
244245
/>
245246
{/* hide until challenge API change is pushed to PROD https://github.com/topcoder-platform/challenge-api/issues/348 */}
246247
{false && <AttachmentField

src/components/ChallengeEditor/TextEditor-Field/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class TextEditorField extends Component {
8484
<ChallengeReviewerField
8585
challenge={challenge}
8686
onUpdateReviewers={this.props.onUpdateOthers}
87+
readOnly={readOnly}
8788
/>
8889
)}
8990
<SpecialChallengeField

0 commit comments

Comments
 (0)