@@ -14,6 +14,7 @@ import classNames from 'classnames'
1414import { TableMobile } from '~/apps/admin/src/lib/components/common/TableMobile'
1515import { IsRemovingType } from '~/apps/admin/src/lib/models'
1616import { MobileTableColumn } from '~/apps/admin/src/lib/models/MobileTableColumn.model'
17+ import { getRatingColor } from '~/libs/core'
1718import { handleError , useWindowSize , WindowSize } from '~/libs/shared'
1819import { IconOutline , Table , TableColumn } from '~/libs/ui'
1920
@@ -34,6 +35,7 @@ import {
3435 aggregateSubmissionReviews ,
3536 challengeHasSubmissionLimit ,
3637 isReviewPhase ,
38+ isReviewPhaseCurrentlyOpen ,
3739 refreshChallengeReviewData ,
3840 REOPEN_MESSAGE_OTHER ,
3941 REOPEN_MESSAGE_SELF ,
@@ -223,6 +225,24 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
223225 [ datas , latestSubmissions , restrictToLatest ] ,
224226 )
225227
228+ if ( process . env . NODE_ENV !== 'production' ) {
229+ try {
230+ console . debug ( '[TableReview] submissionsForAggregation' , submissionsForAggregation . map ( submission => ( {
231+ id : submission . id ,
232+ reviewResourceId : submission . review ?. resourceId ,
233+ reviewHandle : submission . review ?. reviewerHandle ,
234+ reviewId : submission . review ?. id ,
235+ reviews : submission . reviews ?. map ( review => ( {
236+ resourceId : review . resourceId ,
237+ reviewerHandle : review . reviewerHandle ,
238+ score : review . score ,
239+ } ) ) ,
240+ } ) ) )
241+ } catch {
242+ // ignore logging issues
243+ }
244+ }
245+
226246 const aggregatedSubmissionRows = useMemo < AggregatedSubmissionReviews [ ] > ( ( ) => (
227247 aggregateSubmissionReviews ( {
228248 mappingReviewAppeal,
@@ -274,6 +294,70 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
274294 ) ,
275295 [ aggregatedSubmissionRows ] ,
276296 )
297+ interface ReviewerColumnMetadata {
298+ label : string
299+ renderLabel ?: ( ) => JSX . Element
300+ }
301+ const reviewerColumnMetadata = useMemo < ReviewerColumnMetadata [ ] > ( ( ) => (
302+ Array . from ( { length : maxReviewCount } , ( unused , index ) => {
303+ const reviewerDetails = aggregatedSubmissionRows
304+ . map ( aggregated => aggregated . reviews ?. [ index ] )
305+ . filter ( ( detail ) : detail is AggregatedReviewDetail => Boolean ( detail ) )
306+
307+ const resolvedHandle = reviewerDetails
308+ . map ( detail => detail . reviewerHandle ?. trim ( )
309+ || detail . reviewInfo ?. reviewerHandle ?. trim ( )
310+ || undefined )
311+ . find ( ( handle ) : handle is string => Boolean ( handle ) )
312+
313+ if ( ! resolvedHandle ) {
314+ return {
315+ label : `Reviewer ${ index + 1 } ` ,
316+ }
317+ }
318+
319+ const resolvedRating = reviewerDetails
320+ . map ( detail => detail . reviewerMaxRating
321+ ?? detail . reviewInfo ?. reviewerMaxRating
322+ ?? undefined )
323+ . find ( ( rating ) : rating is number => (
324+ typeof rating === 'number'
325+ && Number . isFinite ( rating )
326+ ) )
327+
328+ const resolvedColor = reviewerDetails
329+ . map ( detail => detail . reviewerHandleColor
330+ ?? detail . reviewInfo ?. reviewerHandleColor
331+ ?? undefined )
332+ . find ( ( color ) : color is string => Boolean ( color ) )
333+
334+ const ratingDisplay = resolvedRating !== undefined
335+ ? Math . round ( resolvedRating )
336+ : undefined
337+ const baseLabel = ratingDisplay !== undefined
338+ ? `${ resolvedHandle } (${ ratingDisplay } )`
339+ : resolvedHandle
340+
341+ const renderLabel = ( ) : JSX . Element => {
342+ const color = resolvedColor
343+ ?? ( resolvedRating !== undefined
344+ ? getRatingColor ( resolvedRating )
345+ : undefined )
346+ ?? '#2a2a2a'
347+
348+ return (
349+ < span style = { { color } } >
350+ { baseLabel }
351+ </ span >
352+ )
353+ }
354+
355+ return {
356+ label : baseLabel ,
357+ renderLabel,
358+ }
359+ } )
360+ ) , [ aggregatedSubmissionRows , maxReviewCount ] )
277361
278362 const [ isReopening , setIsReopening ] = useState ( false )
279363 const [ pendingReopen , setPendingReopen ] = useState < PendingReopenState | undefined > ( undefined )
@@ -388,14 +472,14 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
388472 }
389473
390474 const buildPrimaryAction = ( ) : JSX . Element | undefined => {
391- if ( ! hasReviewRole || ! myReviewDetail ) {
475+ if ( ! myReviewDetail ) {
392476 return undefined
393477 }
394478
395479 const reviewInfo = myReviewDetail . reviewInfo
396480 const status = ( reviewInfo ?. status ?? '' ) . toUpperCase ( )
397481
398- if ( status === 'COMPLETED' || status === 'SUBMITTED' ) {
482+ if ( hasReviewRole && ( status === 'COMPLETED' || status === 'SUBMITTED' ) ) {
399483 return (
400484 < div className = { styles . completedAction } key = 'completed-indicator' >
401485 < span className = { styles . completedIcon } >
@@ -408,6 +492,10 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
408492 )
409493 }
410494
495+ if ( ! hasReviewRole ) {
496+ return undefined
497+ }
498+
411499 const reviewId = reviewInfo ?. id ?? myReviewDetail . reviewId
412500 if ( reviewId ) {
413501 return (
@@ -425,6 +513,50 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
425513 return undefined
426514 }
427515
516+ const buildReopenAction = ( ) : JSX . Element | undefined => {
517+ if ( ! myReviewDetail ?. reviewInfo ?. id ) {
518+ return undefined
519+ }
520+
521+ const reviewDetail = myReviewDetail
522+ const reviewInfo = reviewDetail . reviewInfo !
523+ const status = ( reviewInfo . status ?? '' ) . toUpperCase ( )
524+ if ( status !== 'COMPLETED' ) {
525+ return undefined
526+ }
527+
528+ if ( ! isReviewPhaseCurrentlyOpen ( challengeInfo , reviewInfo . phaseId ) ) {
529+ return undefined
530+ }
531+
532+ const resourceId = reviewInfo . resourceId ?? reviewDetail . resourceId
533+ const isOwnReview = resourceId ? myReviewerResourceIds . has ( resourceId ) : false
534+
535+ if ( ! canManageCompletedReviews && ! isOwnReview ) {
536+ return undefined
537+ }
538+
539+ const reviewId = reviewInfo . id
540+ const isPendingReopen = pendingReopen ?. review ?. reviewInfo ?. id === reviewId
541+
542+ function handleReopenClick ( ) : void {
543+ openReopenDialog ( submission , reviewDetail )
544+ }
545+
546+ return (
547+ < button
548+ key = 'reopen-review'
549+ type = 'button'
550+ className = { classNames ( styles . submit , styles . textBlue ) }
551+ onClick = { handleReopenClick }
552+ disabled = { isReopening && isPendingReopen }
553+ >
554+ < i className = 'icon-reopen' />
555+ Reopen Review
556+ </ button >
557+ )
558+ }
559+
428560 const historyKeyForRow = getSubmissionHistoryKey ( submission . memberId , submission . id )
429561 const rowHistory = historyByMember . get ( historyKeyForRow ) ?? [ ]
430562
@@ -453,6 +585,7 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
453585
454586 appendAction ( buildPrimaryAction ( ) , 'primary' )
455587 appendAction ( buildHistoryAction ( ) , 'history' )
588+ appendAction ( buildReopenAction ( ) , 'reopen' )
456589
457590 if ( ! actionEntries . length ) {
458591 return (
@@ -481,11 +614,16 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
481614 </ span >
482615 )
483616 } , [
617+ canManageCompletedReviews ,
484618 canViewHistory ,
619+ challengeInfo ,
485620 handleHistoryButtonClick ,
486621 hasReviewRole ,
487622 historyByMember ,
623+ isReopening ,
488624 myReviewerResourceIds ,
625+ openReopenDialog ,
626+ pendingReopen ,
489627 shouldShowHistoryActions ,
490628 ] )
491629
@@ -557,10 +695,13 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
557695 )
558696
559697 for ( let index = 0 ; index < maxReviewCount ; index += 1 ) {
698+ const metadata = reviewerColumnMetadata [ index ] ?? {
699+ label : `Reviewer ${ index + 1 } ` ,
700+ }
560701 baseColumns . push (
561702 {
562703 columnId : `reviewer-${ index } ` ,
563- label : `Reviewer ${ index + 1 } ` ,
704+ label : metadata . renderLabel ?? metadata . label ,
564705 renderer : ( submission : SubmissionRow ) => renderReviewerCell ( submission , index ) ,
565706 type : 'element' ,
566707 } ,
@@ -606,11 +747,48 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
606747 openReopenDialog ,
607748 challengeInfo ,
608749 pendingReopen ,
750+ reviewerColumnMetadata ,
609751 ] )
610752
611753 const columnsMobile = useMemo < MobileTableColumn < SubmissionRow > [ ] [ ] > (
612754 ( ) => columns . map ( column => {
613- if ( column . label === 'Action' || column . label === 'Actions' ) {
755+ const resolveLabelString = ( ) : string => {
756+ if ( typeof column . label === 'string' ) {
757+ return column . label
758+ }
759+
760+ if ( typeof column . label === 'function' ) {
761+ const labelResult = column . label ( )
762+ if ( typeof labelResult === 'string' ) {
763+ return labelResult
764+ }
765+
766+ const columnId = column . columnId ?? ''
767+ if ( columnId . startsWith ( 'reviewer-' ) ) {
768+ const reviewerIndexRaw = columnId . split ( '-' ) [ 1 ]
769+ const reviewerIndex = reviewerIndexRaw
770+ ? Number . parseInt ( reviewerIndexRaw , 10 )
771+ : NaN
772+ if ( ! Number . isNaN ( reviewerIndex ) ) {
773+ return reviewerColumnMetadata [ reviewerIndex ] ?. label
774+ ?? `Reviewer ${ reviewerIndex + 1 } `
775+ }
776+
777+ return 'Reviewer'
778+ }
779+
780+ return ''
781+ }
782+
783+ return column . label ?? ''
784+ }
785+
786+ const resolvedLabel = resolveLabelString ( )
787+ const labelForAction = typeof column . label === 'string'
788+ ? column . label
789+ : resolvedLabel
790+
791+ if ( labelForAction === 'Action' || labelForAction === 'Actions' ) {
614792 return [
615793 {
616794 ...column ,
@@ -620,15 +798,17 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
620798 ]
621799 }
622800
801+ const labelText = resolvedLabel || ''
802+
623803 return [
624804 {
625805 ...column ,
626806 className : '' ,
627- label : `${ column . label as string } label` ,
807+ label : labelText ? `${ labelText } label` : 'label' ,
628808 mobileType : 'label' ,
629809 renderer : ( ) => (
630810 < div >
631- { column . label as string }
811+ { labelText }
632812 :
633813 </ div >
634814 ) ,
@@ -640,7 +820,7 @@ export const TableReview: FC<TableReviewProps> = (props: TableReviewProps) => {
640820 } ,
641821 ] as MobileTableColumn < SubmissionRow > [ ]
642822 } ) ,
643- [ columns ] ,
823+ [ columns , reviewerColumnMetadata ] ,
644824 )
645825
646826 return (
0 commit comments