Skip to content

Commit de6b71d

Browse files
committed
Update for how we display phases if there are registration and submission phases open - prioritise submission.
1 parent 534bfd9 commit de6b71d

File tree

1 file changed

+180
-12
lines changed

1 file changed

+180
-12
lines changed

src/apps/review/src/lib/components/ChallengePhaseInfo/ChallengePhaseInfo.tsx

Lines changed: 180 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import moment from 'moment'
88

99
import { EnvironmentConfig } from '~/config'
1010

11-
import type { BackendResource, ChallengeInfo, ReviewAppContextModel } from '../../models'
11+
import type { BackendPhase, BackendResource, ChallengeInfo, ReviewAppContextModel } from '../../models'
1212
import type { WinningDetailDto } from '../../services'
1313
import { ChallengeDetailContext, ReviewAppContext } from '../../contexts'
1414
import { useRole, useRoleProps } from '../../hooks'
1515
import { fetchWinningsByExternalId } from '../../services'
1616
import { ProgressBar } from '../ProgressBar'
1717
import { SUBMITTER, TABLE_DATE_FORMAT } from '../../../config/index.config'
18+
import { formatDurationDate } from '../../utils'
1819

1920
import styles from './ChallengePhaseInfo.module.scss'
2021

@@ -156,6 +157,17 @@ export const ChallengePhaseInfo: FC<Props> = (props: Props) => {
156157
isLoadingPayment,
157158
isTopgearTask,
158159
])
160+
const phaseDisplayInfo = useMemo(
161+
() => computePhaseDisplayInfo(props.challengeInfo),
162+
[props.challengeInfo],
163+
)
164+
const {
165+
phaseEndDateString: displayPhaseEndDateString,
166+
phaseLabel: displayPhaseLabel,
167+
timeLeft: displayTimeLeft,
168+
timeLeftColor: displayTimeLeftColor,
169+
timeLeftStatus: displayTimeLeftStatus,
170+
} = phaseDisplayInfo
159171

160172
useEffect(() => {
161173
const run = async (): Promise<void> => {
@@ -219,7 +231,11 @@ export const ChallengePhaseInfo: FC<Props> = (props: Props) => {
219231
const reviewInProgress = props.reviewInProgress
220232

221233
return [
222-
...createPhaseItems(variant, data, isTask),
234+
...createPhaseItems({
235+
displayPhaseLabel,
236+
isTask,
237+
variant,
238+
}),
223239
createRolesItem(myChallengeRoles),
224240
...createTaskItems({
225241
formattedStartDate,
@@ -230,13 +246,17 @@ export const ChallengePhaseInfo: FC<Props> = (props: Props) => {
230246
}),
231247
...createNonTaskItems({
232248
data,
249+
displayPhaseEndDateString,
233250
formattedNonTaskPayment,
234251
isTask,
235252
variant,
236253
walletUrl,
237254
}),
238255
...createActiveItems({
239256
data,
257+
displayTimeLeft,
258+
displayTimeLeftColor,
259+
displayTimeLeftStatus,
240260
isTask,
241261
progressType: PROGRESS_TYPE,
242262
reviewInProgress,
@@ -251,6 +271,11 @@ export const ChallengePhaseInfo: FC<Props> = (props: Props) => {
251271
isTask,
252272
myChallengeRoles,
253273
props.challengeInfo,
274+
displayPhaseLabel,
275+
displayPhaseEndDateString,
276+
displayTimeLeft,
277+
displayTimeLeftColor,
278+
displayTimeLeftStatus,
254279
props.reviewProgress,
255280
props.reviewInProgress,
256281
props.variant,
@@ -311,19 +336,23 @@ export const ChallengePhaseInfo: FC<Props> = (props: Props) => {
311336

312337
export default ChallengePhaseInfo
313338

314-
function createPhaseItems(variant: ChallengeVariant, data: ChallengeInfo, isTask: boolean): ChallengePhaseItem[] {
315-
if (variant !== 'active') {
339+
function createPhaseItems(config: {
340+
displayPhaseLabel: string
341+
isTask: boolean
342+
variant: ChallengeVariant
343+
}): ChallengePhaseItem[] {
344+
if (config.variant !== 'active') {
316345
return []
317346
}
318347

319-
if (isTask) {
348+
if (config.isTask) {
320349
return []
321350
}
322351

323352
return [{
324353
icon: 'icon-review',
325354
title: 'Phase',
326-
value: computeIterativeReviewLabel(data) || data.currentPhase || 'N/A',
355+
value: config.displayPhaseLabel || 'N/A',
327356
}]
328357
}
329358

@@ -385,6 +414,7 @@ function createNonTaskItems(config: {
385414
data: ChallengeInfo
386415
formattedNonTaskPayment?: string
387416
isTask: boolean
417+
displayPhaseEndDateString?: string
388418
variant: ChallengeVariant
389419
walletUrl: string
390420
}): ChallengePhaseItem[] {
@@ -397,7 +427,7 @@ function createNonTaskItems(config: {
397427
title: config.variant === 'past' ? 'Challenge End Date' : 'Phase End Date',
398428
value: config.variant === 'past'
399429
? getChallengeEndDateValue(config.data)
400-
: config.data.currentPhaseEndDateString || '-',
430+
: config.displayPhaseEndDateString || '-',
401431
}]
402432

403433
if (config.formattedNonTaskPayment) {
@@ -421,6 +451,9 @@ function createNonTaskItems(config: {
421451

422452
function createActiveItems(config: {
423453
data: ChallengeInfo
454+
displayTimeLeft?: string
455+
displayTimeLeftColor?: string
456+
displayTimeLeftStatus?: string
424457
progressType: typeof PROGRESS_TYPE
425458
reviewInProgress: boolean
426459
reviewProgress: number
@@ -437,12 +470,12 @@ function createActiveItems(config: {
437470

438471
const items: ChallengePhaseItem[] = [{
439472
icon: 'icon-timer',
440-
status: config.data.timeLeftStatus,
473+
status: config.displayTimeLeftStatus,
441474
style: {
442-
color: config.data.timeLeftColor,
475+
color: config.displayTimeLeftColor,
443476
},
444477
title: 'Time Left',
445-
value: config.data.timeLeft || '-',
478+
value: config.displayTimeLeft || '-',
446479
}]
447480

448481
if (!config.reviewInProgress) {
@@ -456,17 +489,152 @@ function createActiveItems(config: {
456489
return items
457490
}
458491

492+
interface PhaseDisplayInfo {
493+
phaseEndDateString?: string
494+
phaseLabel: string
495+
timeLeft?: string
496+
timeLeftColor?: string
497+
timeLeftStatus?: string
498+
}
499+
500+
function computePhaseDisplayInfo(data?: ChallengeInfo): PhaseDisplayInfo {
501+
const fallbackPhaseLabel = (data?.currentPhase || '').trim() || 'N/A'
502+
const fallback: PhaseDisplayInfo = {
503+
phaseEndDateString: data?.currentPhaseEndDateString,
504+
phaseLabel: fallbackPhaseLabel,
505+
timeLeft: data?.timeLeft,
506+
timeLeftColor: data?.timeLeftColor,
507+
timeLeftStatus: data?.timeLeftStatus,
508+
}
509+
510+
if (!data) {
511+
return fallback
512+
}
513+
514+
const phaseForDisplay = selectPhaseForDisplay(data)
515+
const phaseLabel = computeDisplayPhaseLabel(data, phaseForDisplay) || fallback.phaseLabel
516+
const timing = computePhaseTiming(phaseForDisplay)
517+
518+
return {
519+
phaseEndDateString: timing.endDateString ?? fallback.phaseEndDateString,
520+
phaseLabel,
521+
timeLeft: timing.timeLeft ?? fallback.timeLeft,
522+
timeLeftColor: timing.timeLeftColor ?? fallback.timeLeftColor,
523+
timeLeftStatus: timing.timeLeftStatus ?? fallback.timeLeftStatus,
524+
}
525+
}
526+
527+
function selectPhaseForDisplay(data?: ChallengeInfo): BackendPhase | undefined {
528+
if (!data) return undefined
529+
530+
const phases = Array.isArray(data.phases) ? data.phases : []
531+
if (!phases.length) {
532+
return data.currentPhaseObject
533+
}
534+
535+
const openPhases = phases.filter(phase => phase?.isOpen)
536+
if (!openPhases.length) {
537+
return data.currentPhaseObject
538+
}
539+
540+
const submissionPhase = openPhases.find(phase => normalizePhaseName(phase?.name) === 'submission')
541+
const registrationPhase = openPhases.find(phase => normalizePhaseName(phase?.name) === 'registration')
542+
543+
if (submissionPhase && registrationPhase) {
544+
return submissionPhase
545+
}
546+
547+
if (data.currentPhaseObject && data.currentPhaseObject.isOpen) {
548+
return data.currentPhaseObject
549+
}
550+
551+
return submissionPhase ?? openPhases[0]
552+
}
553+
554+
function computeDisplayPhaseLabel(
555+
data?: ChallengeInfo,
556+
phase?: BackendPhase,
557+
): string {
558+
const fallback = (data?.currentPhase || '').trim() || 'N/A'
559+
560+
if (!data) {
561+
return fallback
562+
}
563+
564+
if (phase && isIterativePhaseName(phase.name)) {
565+
const iterativeLabel = computeIterativeReviewLabel(data, phase)
566+
if (iterativeLabel) {
567+
return iterativeLabel
568+
}
569+
} else if (!phase) {
570+
const iterativeLabel = computeIterativeReviewLabel(data)
571+
if (iterativeLabel) {
572+
return iterativeLabel
573+
}
574+
}
575+
576+
const name = (phase?.name || '').trim()
577+
if (name) {
578+
return name
579+
}
580+
581+
return fallback
582+
}
583+
584+
function computePhaseTiming(phase?: BackendPhase): {
585+
endDateString?: string
586+
timeLeft?: string
587+
timeLeftColor?: string
588+
timeLeftStatus?: string
589+
} {
590+
if (!phase || !phase.isOpen) {
591+
return {}
592+
}
593+
594+
const rawEndDate = phase.actualEndDate || phase.scheduledEndDate
595+
if (!rawEndDate) {
596+
return {}
597+
}
598+
599+
const endDate = new Date(rawEndDate)
600+
if (Number.isNaN(endDate.getTime())) {
601+
return {}
602+
}
603+
604+
const formattedEndDate = moment(endDate)
605+
.local()
606+
.format(TABLE_DATE_FORMAT)
607+
const duration = formatDurationDate(endDate, new Date())
608+
609+
return {
610+
endDateString: formattedEndDate,
611+
timeLeft: duration.durationString,
612+
timeLeftColor: duration.durationColor,
613+
timeLeftStatus: duration.durationStatus,
614+
}
615+
}
616+
617+
function normalizePhaseName(name?: string): string {
618+
return (name || '')
619+
.toString()
620+
.trim()
621+
.toLowerCase()
622+
}
623+
459624
// Helpers extracted to keep component complexity manageable
460625
function isIterativePhaseName(name?: string): boolean {
461626
return typeof name === 'string' && name.trim()
462627
.toLowerCase()
463628
.includes('iterative review')
464629
}
465630

466-
function computeIterativeReviewLabel(data: any): string | undefined {
631+
function computeIterativeReviewLabel(
632+
data: any,
633+
overridePhase?: BackendPhase,
634+
): string | undefined {
467635
const phases = Array.isArray(data?.phases) ? data.phases : []
468636

469-
const current = data?.currentPhaseObject
637+
const current = overridePhase ?? data?.currentPhaseObject
470638
const currentIsIterative = isIterativePhaseName(current?.name)
471639

472640
const openIterative = currentIsIterative

0 commit comments

Comments
 (0)