diff --git a/src/shared/components/challenge-listing/ChallengeCard/Status/index.jsx b/src/shared/components/challenge-listing/ChallengeCard/Status/index.jsx index 3cb06f6c46..d3315863aa 100644 --- a/src/shared/components/challenge-listing/ChallengeCard/Status/index.jsx +++ b/src/shared/components/challenge-listing/ChallengeCard/Status/index.jsx @@ -6,6 +6,7 @@ import LeaderboardAvatar from 'components/challenge-listing/LeaderboardAvatar'; import { config, Link } from 'topcoder-react-utils'; import { TABS as DETAIL_TABS } from 'actions/page/challenge-details'; import 'moment-duration-format'; +import { phaseEndDate } from 'utils/challenge-listing/helper'; import { getTimeLeft, } from 'utils/challenge-detail/helper'; @@ -271,7 +272,7 @@ export default function ChallengeStatus(props) {
{getTimeLeft(statusPhase, 'to go').text} diff --git a/src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx b/src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx index 675e181bc4..432afcde8b 100644 --- a/src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx +++ b/src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx @@ -17,6 +17,7 @@ import _ from 'lodash'; import React from 'react'; import PT from 'prop-types'; import Tooltip from 'components/Tooltip'; +import { phaseStartDate, phaseEndDate } from 'utils/challenge-listing/helper'; import LoaderIcon from '../../../Loader/Loader'; import './style.scss'; @@ -95,31 +96,32 @@ function Tip(props) { if (!c || _.isEmpty(c)) return
; const allPhases = c.phases || []; - const endPhaseDate = Math.max(...allPhases.map(d => new Date(d.scheduledEndDate))); + const endPhaseDate = Math.max(...allPhases.map(d => phaseEndDate(d))); const registrationPhase = allPhases.find(phase => phase.name === 'Registration'); const submissionPhase = allPhases.find(phase => phase.name === 'Submission'); + const checkpointPhase = allPhases.find(phase => phase.name === 'Checkpoint Submission'); if (registrationPhase) { steps.push({ - date: new Date(registrationPhase.scheduledStartDate), + date: phaseStartDate(registrationPhase), name: 'Start', }); } - if (c.checkpointSubmissionEndDate) { + if (checkpointPhase) { steps.push({ - date: new Date(c.checkpointSubmissionEndDate), + date: phaseEndDate(checkpointPhase), name: 'Checkpoint', }); } const iterativeReviewPhase = allPhases.find(phase => phase.isOpen && phase.name === 'Iterative Review'); if (iterativeReviewPhase) { steps.push({ - date: new Date(iterativeReviewPhase.scheduledEndDate), + date: phaseEndDate(iterativeReviewPhase), name: 'Iterative Review', }); } else if (submissionPhase) { steps.push({ - date: new Date(submissionPhase.scheduledEndDate), + date: phaseEndDate(submissionPhase), name: 'Submission', }); } diff --git a/src/shared/utils/challenge-detail/helper.jsx b/src/shared/utils/challenge-detail/helper.jsx index 2a6fb4b8be..38b8317f90 100644 --- a/src/shared/utils/challenge-detail/helper.jsx +++ b/src/shared/utils/challenge-detail/helper.jsx @@ -9,6 +9,7 @@ import { challenge as challengeUtils } from 'topcoder-react-lib'; import { config } from 'topcoder-react-utils'; import Prize from 'components/challenge-listing/ChallengeCard/Prize'; import { BUCKETS, getBuckets } from 'utils/challenge-listing/buckets'; +import { phaseEndDate } from 'utils/challenge-listing/helper'; const Filter = challengeUtils.filter; @@ -42,7 +43,7 @@ export function getEndDate(challenge) { if (type === 'First2Finish' && challenge.status === 'Completed') { phases = challenge.phases.filter(p => p.phaseType === 'Iterative Review' && p.phaseStatus === 'Closed'); } - const endPhaseDate = Math.max(...phases.map(d => new Date(d.scheduledEndDate))); + const endPhaseDate = Math.max(...phases.map(d => phaseEndDate(d))); return moment(endPhaseDate).format('MMM DD'); } @@ -65,7 +66,7 @@ export function getTimeLeft( return { late: false, text: FF_TIME_LEFT_MSG }; } - let time = moment(phase.scheduledEndDate).diff(); + let time = moment(phaseEndDate(phase)).diff(); const late = time < 0; if (late) time = -time; diff --git a/src/shared/utils/challenge-listing/helper.js b/src/shared/utils/challenge-listing/helper.js new file mode 100644 index 0000000000..192dafcc04 --- /dev/null +++ b/src/shared/utils/challenge-listing/helper.js @@ -0,0 +1,31 @@ +import moment from 'moment'; + +/** + * Returns phase's end date. + * @param {Object} phase + * @return {Date} + */ +export function phaseEndDate(phase) { + // Case 1: phase is still open. take the `scheduledEndDate` + // Case 2: phase is not open but `scheduledStartDate` is a future date. + // This means phase is not yet started. So take the `scheduledEndDate` + if (phase.isOpen || moment(phase.scheduledStartDate).isAfter()) { + return new Date(phase.scheduledEndDate); + } + // for other cases, take the `actualEndDate` as phase is already closed + return new Date(phase.actualEndDate); +} + +/** + * Returns phase's start date. + * @param {Object} phase + * @return {Date} + */ +export function phaseStartDate(phase) { + // Case 1: Phase is not yet started. take the `scheduledStartDate` + if (phase.isOpen !== true && moment(phase.scheduledStartDate).isAfter()) { + return new Date(phase.scheduledStartDate); + } + // For all other cases, take the `actualStartDate` as phase is already started + return new Date(phase.actualStartDate); +} diff --git a/src/shared/utils/challenge-listing/sort.js b/src/shared/utils/challenge-listing/sort.js index 4ef586d1f8..bf6b52818f 100644 --- a/src/shared/utils/challenge-listing/sort.js +++ b/src/shared/utils/challenge-listing/sort.js @@ -4,6 +4,7 @@ import moment from 'moment'; import { find, sumBy } from 'lodash'; +import { phaseStartDate, phaseEndDate } from './helper'; export const SORTS = { CURRENT_PHASE: 'current-phase', @@ -26,13 +27,21 @@ export default { }, [SORTS.MOST_RECENT]: { func: (a, b) => { - const getRegistrationStartDate = (challenge) => { + const getChallengeStartDate = (challenge) => { + // extract the phases from `challenge.phases`, + // as `challenge.registrationStartDate` returned from API is not reliable const registrationPhase = find(challenge.phases, p => p.name === 'Registration'); - return registrationPhase.actualStartDate || registrationPhase.scheduledStartDate; + const submissionPhase = find(challenge.phases, p => p.name === 'Submission'); + // registration phase exists + if (registrationPhase) { + return moment(phaseStartDate(registrationPhase)); + } + // registration phase doesnt exist, This is possibly a F2F or TSK. Take submission phase + return moment(phaseStartDate(submissionPhase)); }; - const aRegistrationStartDate = getRegistrationStartDate(a); - const bRegistrationStartDate = getRegistrationStartDate(b); - return moment(bRegistrationStartDate).diff(aRegistrationStartDate); + const aChallengeStartDate = getChallengeStartDate(a); + const bChallengeStartDate = getChallengeStartDate(b); + return bChallengeStartDate.diff(aChallengeStartDate); }, name: 'Most recent', }, @@ -51,12 +60,20 @@ export default { [SORTS.TIME_TO_REGISTER]: { func: (a, b) => { const getRegistrationEndDate = (challenge) => { + // extract the registration phase from `challenge.phases`, + // as `challenge.registrationEndDate` returned from API is not reliable const registrationPhase = find(challenge.phases, p => p.name === 'Registration'); - return registrationPhase.actualEndDate || registrationPhase.scheduledEndDate; + const submissionPhase = find(challenge.phases, p => p.name === 'Submission'); + // case 1: registration phase exists + if (registrationPhase) { + return moment(phaseEndDate(registrationPhase)); + } + // case 2: registration phase doesn't exist. Take submission phase instead. + return moment(phaseEndDate(submissionPhase)); }; - const aDate = moment(getRegistrationEndDate(a) || a.submissionEndTimestamp); - const bDate = moment(getRegistrationEndDate(b) || b.submissionEndTimestamp); + const aDate = getRegistrationEndDate(a); + const bDate = getRegistrationEndDate(b); if (aDate.isBefore() && bDate.isAfter()) return 1; if (aDate.isAfter() && bDate.isBefore()) return -1; @@ -68,11 +85,23 @@ export default { }, [SORTS.TIME_TO_SUBMIT]: { func: (a, b) => { - function nextSubEndDate(o) { - if (o.checkpointSubmissionEndDate && moment(o.checkpointSubmissionEndDate).isAfter()) { - return moment(o.checkpointSubmissionEndDate); + function nextSubEndDate(challenge) { + // extract the submission and checkpoint (if any) phases from `challenge.phases`, + // as `challenge.submissionEndDate` returned from API is not reliable + const checkpointPhase = find(challenge.phases, p => p.name === 'Checkpoint Submission'); + const submissionPhase = find(challenge.phases, p => p.name === 'Submission'); + // Case 1: challenge has checkpoint submission phase + if (!!checkpointPhase === true) { + // Case 1.1: checkpoint submission phase is still open. + // then take the `scheduledEndDate` of this phase. + // Case 1.2: checkpoint submission phase is closed + // but its `scheduledStartDate` is a future date. + // This means this phase is not yet started. Take the `scheduledEndDate` of this phase. + if (checkpointPhase.isOpen || moment(checkpointPhase.scheduledStartDate).isAfter()) { + return moment(checkpointPhase.scheduledEndDate); + } } - return moment(o.submissionEndTimestamp); + return moment(phaseEndDate(submissionPhase)); } const aDate = nextSubEndDate(a);