@@ -25,7 +25,7 @@ import {
2525 MILESTONE_STATUS ,
2626 PHASE_PRODUCT_CHALLENGE_ID_FIELD ,
2727 QA_TRACK_ID , DESIGN_CHALLENGE_TYPES , ROUND_TYPES ,
28- MULTI_ROUND_CHALLENGE_TEMPLATE_ID , DS_TRACK_ID ,
28+ MULTI_ROUND_CHALLENGE_TEMPLATE_ID ,
2929 CHALLENGE_STATUS ,
3030 SKILLS_OPTIONAL_BILLING_ACCOUNT_IDS
3131} from '../../config/constants'
@@ -148,6 +148,7 @@ class ChallengeEditor extends Component {
148148 this . onSaveChallenge = this . onSaveChallenge . bind ( this )
149149 this . getCurrentTemplate = this . getCurrentTemplate . bind ( this )
150150 this . onUpdateMetadata = this . onUpdateMetadata . bind ( this )
151+ this . shouldShowDashboardSetting = this . shouldShowDashboardSetting . bind ( this )
151152 this . getTemplatePhases = this . getTemplatePhases . bind ( this )
152153 this . getAvailableTimelineTemplates = this . getAvailableTimelineTemplates . bind ( this )
153154 this . autoUpdateChallengeThrottled = _ . throttle ( this . validateAndAutoUpdateChallenge . bind ( this ) , 3000 ) // 3s
@@ -669,6 +670,20 @@ class ChallengeEditor extends Component {
669670 this . setState ( { challenge : newChallenge } )
670671 }
671672
673+ /**
674+ * Determines when the data dashboard toggle should be shown.
675+ *
676+ * @param {Object } challenge the challenge data to evaluate
677+ */
678+ shouldShowDashboardSetting ( challenge = { } ) {
679+ const typeId = _ . get ( challenge , 'typeId' )
680+ const metadata = _ . get ( challenge , 'metadata' , [ ] )
681+ const hasDashboardMetadata = _ . some ( metadata , { name : 'show_data_dashboard' } )
682+ const isMarathonMatch = typeId === MARATHON_TYPE_ID
683+
684+ return isMarathonMatch || hasDashboardMetadata
685+ }
686+
672687 /**
673688 * Remove Phase from challenge Phases list
674689 * @param index
@@ -897,11 +912,47 @@ class ChallengeEditor extends Component {
897912 return ! ( isRequiredMissing || _ . isEmpty ( this . state . currentTemplate ) )
898913 }
899914
915+ // Return array of phase names that have more than one manual (member) reviewer configured.
916+ // If none, returns empty array.
917+ getDuplicateManualReviewerPhases ( ) {
918+ const { challenge } = this . state
919+ const reviewers = ( challenge && challenge . reviewers ) || [ ]
920+ const phases = ( challenge && challenge . phases ) || [ ]
921+
922+ const counts = { }
923+ reviewers . forEach ( r => {
924+ if ( r && ( r . isMemberReview !== false ) && r . phaseId ) {
925+ const pid = String ( r . phaseId )
926+ counts [ pid ] = ( counts [ pid ] || 0 ) + 1
927+ }
928+ } )
929+
930+ const duplicatedPhaseIds = Object . keys ( counts ) . filter ( pid => counts [ pid ] > 1 )
931+ if ( duplicatedPhaseIds . length === 0 ) return [ ]
932+
933+ return duplicatedPhaseIds . map ( pid => {
934+ const p = phases . find ( ph => String ( ph . phaseId || ph . id ) === pid )
935+ return p ? ( p . name || pid ) : pid
936+ } )
937+ }
938+
900939 validateChallenge ( ) {
901940 if ( this . isValidChallenge ( ) ) {
941+ // Additional validation: block saving draft if there are duplicate manual reviewer configs per phase
942+ const duplicates = this . getDuplicateManualReviewerPhases ( )
943+ if ( duplicates && duplicates . length > 0 ) {
944+ const message = `Duplicate manual reviewer configuration found for phase(s): ${ duplicates . join ( ', ' ) } . Only one manual reviewer configuration is allowed per phase.`
945+ this . setState ( { hasValidationErrors : true , error : message } )
946+ return false
947+ }
948+
949+ if ( this . state . error ) {
950+ this . setState ( { error : null } )
951+ }
902952 this . setState ( { hasValidationErrors : false } )
903953 return true
904954 }
955+
905956 this . setState ( prevState => ( {
906957 ...prevState ,
907958 challenge : {
@@ -1095,11 +1146,7 @@ class ChallengeEditor extends Component {
10951146 const { challenge : { name, trackId, typeId, milestoneId, roundType, challengeType, metadata : challengeMetadata } } = this . state
10961147 const { timelineTemplates } = metadata
10971148 const isDesignChallenge = trackId === DES_TRACK_ID
1098- const isDataScience = trackId === DS_TRACK_ID
1099- const isChallengeType = typeId === CHALLENGE_TYPE_ID
1100- const isDevChallenge = trackId === DEV_TRACK_ID
1101- const isMM = typeId === MARATHON_TYPE_ID
1102- const showDashBoard = ( isDataScience && isChallengeType ) || ( isDevChallenge && isMM ) || ( isDevChallenge && isChallengeType )
1149+ const showDashBoard = this . shouldShowDashboardSetting ( { trackId, typeId, metadata : challengeMetadata } )
11031150
11041151 // indicate that creating process has started
11051152 this . setState ( { isSaving : true } )
@@ -1754,18 +1801,33 @@ class ChallengeEditor extends Component {
17541801 const showTimeline = false // disables the timeline for time being https://github.com/topcoder-platform/challenge-engine-ui/issues/706
17551802 const copilotResources = metadata . members || challengeResources
17561803 const isDesignChallenge = challenge . trackId === DES_TRACK_ID
1757- const isDevChallenge = challenge . trackId === DEV_TRACK_ID
1758- const isMM = challenge . typeId === MARATHON_TYPE_ID
17591804 const isChallengeType = challenge . typeId === CHALLENGE_TYPE_ID
17601805 const showRoundType = isDesignChallenge && isChallengeType
17611806 const showCheckpointPrizes = challenge . timelineTemplateId === MULTI_ROUND_CHALLENGE_TEMPLATE_ID
1762- const showDashBoard = ( challenge . trackId === DS_TRACK_ID && isChallengeType ) || ( isDevChallenge && isMM ) || ( isDevChallenge && isChallengeType )
17631807 const useDashboardData = _ . find ( challenge . metadata , { name : 'show_data_dashboard' } )
1808+ const showDashBoard = this . shouldShowDashboardSetting ( challenge )
17641809
17651810 const useDashboard = useDashboardData
17661811 ? ( _ . isString ( useDashboardData . value ) && useDashboardData . value === 'true' ) ||
17671812 ( _ . isBoolean ( useDashboardData . value ) && useDashboardData . value ) : false
17681813
1814+ const dashboardToggle = showDashBoard && (
1815+ < div className = { styles . row } >
1816+ < div className = { cn ( styles . field , styles . col1 ) } >
1817+ < label htmlFor = 'isDashboardEnabled' > Use data dashboard :</ label >
1818+ </ div >
1819+ < div className = { cn ( styles . field , styles . col2 ) } >
1820+ < input
1821+ name = 'isDashboardEnabled'
1822+ type = 'checkbox'
1823+ id = 'isDashboardEnabled'
1824+ checked = { useDashboard }
1825+ onChange = { ( e ) => this . onUpdateMetadata ( 'show_data_dashboard' , e . target . checked ) }
1826+ />
1827+ </ div >
1828+ </ div >
1829+ )
1830+
17691831 const workTypes = getDomainTypes ( challenge . trackId )
17701832 let filteredTypes = metadata . challengeTypes . filter ( type => workTypes . includes ( type . abbreviation ) )
17711833
@@ -1794,22 +1856,7 @@ class ChallengeEditor extends Component {
17941856 }
17951857 < ChallengeNameField challenge = { challenge } onUpdateInput = { this . onUpdateInput } />
17961858 {
1797- showDashBoard && (
1798- < div className = { styles . row } >
1799- < div className = { cn ( styles . field , styles . col1 ) } >
1800- < label htmlFor = 'isDashboardEnabled' > Use data dashboard :</ label >
1801- </ div >
1802- < div className = { cn ( styles . field , styles . col2 ) } >
1803- < input
1804- name = 'isDashboardEnabled'
1805- type = 'checkbox'
1806- id = 'isDashboardEnabled'
1807- checked = { useDashboard }
1808- onChange = { ( e ) => this . onUpdateMetadata ( 'show_data_dashboard' , e . target . checked ) }
1809- />
1810- </ div >
1811- </ div >
1812- )
1859+ dashboardToggle
18131860 }
18141861 { projectDetail . version === 'v4' && < MilestoneField milestones = { activeProjectMilestones } onUpdateSelect = { this . onUpdateSelect } projectId = { projectDetail . id } selectedMilestoneId = { selectedMilestoneId } /> }
18151862 { useTask && ( < DiscussionField hasForum = { hasForum } toggleForum = { this . toggleForumOnCreate } /> ) }
@@ -1841,24 +1888,6 @@ class ChallengeEditor extends Component {
18411888 </ div >
18421889
18431890 < ChallengeNameField challenge = { challenge } onUpdateInput = { this . onUpdateInput } />
1844- {
1845- showDashBoard && (
1846- < div className = { styles . row } >
1847- < div className = { cn ( styles . field , styles . col1 ) } >
1848- < label htmlFor = 'isDashboardEnabled' > Use data dashboard :</ label >
1849- </ div >
1850- < div className = { cn ( styles . field , styles . col2 ) } >
1851- < input
1852- name = 'isDashboardEnabled'
1853- type = 'checkbox'
1854- id = 'isDashboardEnabled'
1855- checked = { useDashboard }
1856- onChange = { ( e ) => this . onUpdateMetadata ( 'show_data_dashboard' , e . target . checked ) }
1857- />
1858- </ div >
1859- </ div >
1860- )
1861- }
18621891 { isTask && (
18631892 < AssignedMemberField
18641893 challenge = { challenge }
@@ -1890,6 +1919,7 @@ class ChallengeEditor extends Component {
18901919 </ div >
18911920 { isOpenAdvanceSettings && (
18921921 < React . Fragment >
1922+ { dashboardToggle }
18931923 < NDAField challenge = { challenge } toggleNdaRequire = { this . toggleNdaRequire } />
18941924 { /* remove terms field and use default term */ }
18951925 { false && ( < TermsField terms = { metadata . challengeTerms } challenge = { challenge } onUpdateMultiSelect = { this . onUpdateMultiSelect } /> ) }
0 commit comments