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);