diff --git a/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js b/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js index 288d4d1f..5fbafd29 100644 --- a/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js +++ b/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js @@ -444,41 +444,6 @@ class ChallengeReviewerField extends Component { const isAIReviewer = this.isAIReviewer(defaultTrackReviewer) - // Prevent adding a second manual (member) reviewer for the same phase. - // If the default phase already has a manual reviewer, attempt to find another - // suitable review phase that does not yet have a manual reviewer and use it. - if (!isAIReviewer && defaultPhaseId) { - const existsManualForPhase = currentReviewers.some(r => (r.isMemberReview !== false) && (r.phaseId === defaultPhaseId)) - if (existsManualForPhase) { - const possibleAlternatePhase = (challenge.phases || []).find(p => { - const rawName = p.name ? p.name : '' - const phaseName = rawName.toLowerCase() - const phaseWithoutHyphens = phaseName.replace(/[-\s]/g, '') - const acceptedPhases = ['review', 'screening', 'checkpointscreening', 'approval', 'postmortem'] - const isSubmissionPhase = phaseName.includes('submission') - const acceptable = acceptedPhases.includes(phaseWithoutHyphens) && !isSubmissionPhase - - if (!acceptable) return false - - const phaseId = p.phaseId || p.id - const used = currentReviewers.some(r => (r.isMemberReview !== false) && (r.phaseId === phaseId)) - return !used - }) - - if (possibleAlternatePhase) { - defaultPhaseId = possibleAlternatePhase.phaseId || possibleAlternatePhase.id - if (this.state.error) this.setState({ error: null }) - } else { - const phase = (challenge.phases || []).find(p => (p.id === defaultPhaseId) || (p.phaseId === defaultPhaseId)) - const phaseName = phase ? (phase.name || defaultPhaseId) : defaultPhaseId - this.setState({ - error: `A manual reviewer configuration already exists for phase '${phaseName}'` - }) - return - } - } - } - // For AI reviewers, get scorecardId from the workflow if available let scorecardId = '' if (isAIReviewer) { diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index da51eb04..6516403f 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -897,11 +897,47 @@ class ChallengeEditor extends Component { return !(isRequiredMissing || _.isEmpty(this.state.currentTemplate)) } + // Return array of phase names that have more than one manual (member) reviewer configured. + // If none, returns empty array. + getDuplicateManualReviewerPhases () { + const { challenge } = this.state + const reviewers = (challenge && challenge.reviewers) || [] + const phases = (challenge && challenge.phases) || [] + + const counts = {} + reviewers.forEach(r => { + if (r && (r.isMemberReview !== false) && r.phaseId) { + const pid = String(r.phaseId) + counts[pid] = (counts[pid] || 0) + 1 + } + }) + + const duplicatedPhaseIds = Object.keys(counts).filter(pid => counts[pid] > 1) + if (duplicatedPhaseIds.length === 0) return [] + + return duplicatedPhaseIds.map(pid => { + const p = phases.find(ph => String(ph.phaseId || ph.id) === pid) + return p ? (p.name || pid) : pid + }) + } + validateChallenge () { if (this.isValidChallenge()) { + // Additional validation: block saving draft if there are duplicate manual reviewer configs per phase + const duplicates = this.getDuplicateManualReviewerPhases() + if (duplicates && duplicates.length > 0) { + const message = `Duplicate manual reviewer configuration found for phase(s): ${duplicates.join(', ')}. Only one manual reviewer configuration is allowed per phase.` + this.setState({ hasValidationErrors: true, error: message }) + return false + } + + if (this.state.error) { + this.setState({ error: null }) + } this.setState({ hasValidationErrors: false }) return true } + this.setState(prevState => ({ ...prevState, challenge: {