diff --git a/.circleci/config.yml b/.circleci/config.yml
index bc9e9d23..0d96a6c9 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -150,7 +150,7 @@ workflows:
context : org-global
filters: &filters-dev
branches:
- only: ['develop', 'hot-fix-jira-vuln-2333']
+ only: ['develop']
# Production builds are exectuted only on tagged commits to the
# master branch.
diff --git a/package-lock.json b/package-lock.json
index d5c828dc..65c76e8a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1142,6 +1142,17 @@
"glob-to-regexp": "^0.3.0"
}
},
+ "@nateradebaugh/react-datetime": {
+ "version": "4.4.11",
+ "resolved": "https://registry.npmjs.org/@nateradebaugh/react-datetime/-/react-datetime-4.4.11.tgz",
+ "integrity": "sha512-PO/j7v8cb0Aw/MwokOL07jyYAnwkvLp9Y9n+cR4Rqr4bM051uj9VHufuXQduMj1BKAfIMNIIrhb4NhcGCnjwow==",
+ "requires": {
+ "@reach/popover": "0.16.2",
+ "classcat": "^5.0.1",
+ "date-fns": "^2.28.0",
+ "use-onclickoutside": "^0.4.0"
+ }
+ },
"@nodelib/fs.stat": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
@@ -1152,6 +1163,82 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.5.4.tgz",
"integrity": "sha512-ZpKr+WTb8zsajqgDkvCEWgp6d5eJT6Q63Ng2neTbzBO76Lbe91vX/iVIW9dikq+Fs3yEo+ls4cxeXABD2LtcbQ=="
},
+ "@reach/observe-rect": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz",
+ "integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ=="
+ },
+ "@reach/popover": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@reach/popover/-/popover-0.16.2.tgz",
+ "integrity": "sha512-IwkRrHM7Vt33BEkSXneovymJv7oIToOfTDwRKpuYEB/BWYMAuNfbsRL7KVe6MjkgchDeQzAk24cYY1ztQj5HQQ==",
+ "requires": {
+ "@reach/portal": "0.16.2",
+ "@reach/rect": "0.16.0",
+ "@reach/utils": "0.16.0",
+ "tabbable": "^4.0.0",
+ "tslib": "^2.3.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ }
+ }
+ },
+ "@reach/portal": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.16.2.tgz",
+ "integrity": "sha512-9ur/yxNkuVYTIjAcfi46LdKUvH0uYZPfEp4usWcpt6PIp+WDF57F/5deMe/uGi/B/nfDweQu8VVwuMVrCb97JQ==",
+ "requires": {
+ "@reach/utils": "0.16.0",
+ "tiny-warning": "^1.0.3",
+ "tslib": "^2.3.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ }
+ }
+ },
+ "@reach/rect": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@reach/rect/-/rect-0.16.0.tgz",
+ "integrity": "sha512-/qO9jQDzpOCdrSxVPR6l674mRHNTqfEjkaxZHluwJ/2qGUtYsA0GSZiF/+wX/yOWeBif1ycxJDa6HusAMJZC5Q==",
+ "requires": {
+ "@reach/observe-rect": "1.2.0",
+ "@reach/utils": "0.16.0",
+ "prop-types": "^15.7.2",
+ "tiny-warning": "^1.0.3",
+ "tslib": "^2.3.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ }
+ }
+ },
+ "@reach/utils": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.16.0.tgz",
+ "integrity": "sha512-PCggBet3qaQmwFNcmQ/GqHSefadAFyNCUekq9RrWoaU9hh/S4iaFgf2MBMdM47eQj5i/Bk0Mm07cP/XPFlkN+Q==",
+ "requires": {
+ "tiny-warning": "^1.0.3",
+ "tslib": "^2.3.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ }
+ }
+ },
"@sentry/hub": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.28.0.tgz",
@@ -1822,6 +1909,11 @@
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
+ "are-passive-events-supported": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/are-passive-events-supported/-/are-passive-events-supported-1.1.1.tgz",
+ "integrity": "sha512-5wnvlvB/dTbfrCvJ027Y4L4gW/6Mwoy1uFSavney0YO++GU+0e/flnjiBBwH+1kh7xNCgCOGvmJC3s32joYbww=="
+ },
"are-we-there-yet": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
@@ -3455,6 +3547,11 @@
}
}
},
+ "classcat": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.3.tgz",
+ "integrity": "sha512-6dK2ke4VEJZOFx2ZfdDAl5OhEL8lvkl6EHF92IfRePfHxQTqir5NlcNVUv+2idjDqCX2NDc8m8YSAI5NI975ZQ=="
+ },
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
@@ -4348,6 +4445,11 @@
"whatwg-url": "^8.0.0"
}
},
+ "date-fns": {
+ "version": "2.28.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz",
+ "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw=="
+ },
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -9829,6 +9931,14 @@
"resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.3.2.tgz",
"integrity": "sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ=="
},
+ "moment-timezone": {
+ "version": "0.5.34",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz",
+ "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==",
+ "requires": {
+ "moment": ">= 2.9.0"
+ }
+ },
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -17430,6 +17540,11 @@
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
},
+ "tabbable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz",
+ "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ=="
+ },
"table": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz",
@@ -18079,6 +18194,28 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
+ "use-isomorphic-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ=="
+ },
+ "use-latest": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz",
+ "integrity": "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==",
+ "requires": {
+ "use-isomorphic-layout-effect": "^1.0.0"
+ }
+ },
+ "use-onclickoutside": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/use-onclickoutside/-/use-onclickoutside-0.4.0.tgz",
+ "integrity": "sha512-lg1U+V8SaCfemgBs5dg+cfEOzjuwVS9ATH0VMLSBHI6R11tbfmiKci1lg6pjwXr1sj95XWd2+5EbffJEAPdkJQ==",
+ "requires": {
+ "are-passive-events-supported": "^1.1.0",
+ "use-latest": "^1.0.0"
+ }
+ },
"util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
diff --git a/package.json b/package.json
index 21b87380..02e50216 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.14",
"@fortawesome/free-solid-svg-icons": "^5.7.1",
"@fortawesome/react-fontawesome": "^0.1.4",
+ "@nateradebaugh/react-datetime": "^4.4.11",
"@popperjs/core": "^2.5.4",
"@svgr/webpack": "2.4.1",
"axios": "^0.19.0",
@@ -53,6 +54,7 @@
"mini-css-extract-plugin": "0.4.3",
"moment": "^2.24.0",
"moment-duration-format": "^2.2.2",
+ "moment-timezone": "^0.5.34",
"node-sass": "^4.14.0",
"optimize-css-assets-webpack-plugin": "5.0.1",
"pnp-webpack-plugin": "1.1.0",
diff --git a/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js b/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js
index 328477d2..3abd88b9 100644
--- a/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js
+++ b/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js
@@ -6,7 +6,7 @@ import $ from 'jquery'
import styles from './ChallengeSchedule-Field.module.scss'
import cn from 'classnames'
import jstz from 'jstimezonedetect'
-import PhaseInput from '../../PhaseInput'
+import StartDateInput from '../../StartDateInput'
import Chart from 'react-google-charts'
import Select from '../../Select'
import { parseSVG } from '../../../util/svg'
@@ -183,7 +183,7 @@ class ChallengeScheduleField extends Component {
return (
_.map(challenge.phases, (p, index) => (
}
-
const showTimeline = false // disables the timeline for time being https://github.com/topcoder-platform/challenge-engine-ui/issues/706
const isTask = _.get(challenge, 'task.isTask', false)
+ const phases = _.get(challenge, 'phases', [])
return (
@@ -188,16 +189,14 @@ const ChallengeView = ({
>
)}
{
-
+ phases.map((phase, index) => (
-
+ ))
}
{showTimeline && (
{
+ this.onTick()
+ }, 500)
+ this.intervalId = setInterval(() => {
+ this.onTick()
+ }, 60000)
+ }
+
+ componentDidUnMount () {
+ clearInterval(this.intervalId)
}
componentDidUpdate () {
@@ -810,6 +821,65 @@ class ChallengeEditor extends Component {
this.setState({ challenge: newChallenge })
}
+ onTick () {
+ if (this.state && this.state) {
+ const { phases } = this.state.challenge
+ let newChallenge = _.cloneDeep(this.state.challenge)
+ for (let index = 0; index < phases.length; ++index) {
+ newChallenge.phases[index].isDurationActive =
+ moment(newChallenge.phases[index]['scheduledEndDate']).isAfter()
+ newChallenge.phases[index].isStartTimeActive = index > 0 ? false
+ : moment(newChallenge.phases[0]['scheduledStartDate']).isAfter()
+ newChallenge.phases[index].isOpen =
+ newChallenge.phases[index].isDurationActive
+ }
+ this.setState({ challenge: newChallenge })
+ }
+ }
+
+ onUpdatePhaseDate (phase, index) {
+ const { phases } = this.state.challenge
+ let newChallenge = _.cloneDeep(this.state.challenge)
+ if (phase.isBlur && newChallenge.phases[index]['name'] === 'Submission') {
+ newChallenge.phases[index]['duration'] = _.max([
+ newChallenge.phases[index - 1]['duration'],
+ phase.duration
+ ])
+ newChallenge.phases[index]['scheduledEndDate'] =
+ moment(newChallenge.phases[index]['scheduledStartDate'])
+ .add(newChallenge.phases[index]['duration'], 'hours')
+ .format('MM/DD/YYYY HH:mm')
+ } else {
+ newChallenge.phases[index]['duration'] = phase.duration
+ newChallenge.phases[index]['scheduledStartDate'] = phase.startDate
+ newChallenge.phases[index]['scheduledEndDate'] = phase.endDate
+ }
+
+ for (let phaseIndex = index + 1; phaseIndex < phases.length; ++phaseIndex) {
+ if (newChallenge.phases[phaseIndex]['name'] === 'Submission') {
+ newChallenge.phases[phaseIndex]['scheduledStartDate'] =
+ newChallenge.phases[phaseIndex - 1]['scheduledStartDate']
+ newChallenge.phases[phaseIndex]['duration'] = _.max([
+ newChallenge.phases[phaseIndex - 1]['duration'],
+ newChallenge.phases[phaseIndex]['duration']
+ ])
+ } else {
+ newChallenge.phases[phaseIndex]['scheduledStartDate'] =
+ newChallenge.phases[phaseIndex - 1]['scheduledEndDate']
+ }
+ newChallenge.phases[phaseIndex]['scheduledEndDate'] =
+ moment(newChallenge.phases[phaseIndex]['scheduledStartDate'])
+ .add(newChallenge.phases[phaseIndex]['duration'], 'hours')
+ .format('MM/DD/YYYY HH:mm')
+ }
+
+ this.setState({ challenge: newChallenge })
+
+ setTimeout(() => {
+ this.onTick()
+ }, 500)
+ }
+
collectChallengeData (status) {
const { attachments, metadata } = this.props
const challenge = pick([
@@ -851,7 +921,9 @@ class ChallengeEditor extends Component {
}
challenge.phases = challenge.phases.map((p) => pick([
'duration',
- 'phaseId'
+ 'phaseId',
+ 'scheduledStartDate',
+ 'scheduledEndDate'
], p))
if (challenge.terms && challenge.terms.length === 0) delete challenge.terms
delete challenge.attachments
@@ -1280,7 +1352,7 @@ class ChallengeEditor extends Component {
let closeTaskModal = null
let draftModal = null
- let { type } = challenge
+ let { type, phases = [] } = challenge
if (!type) {
const { typeId } = challenge
if (typeId && metadata.challengeTypes) {
@@ -1561,20 +1633,22 @@ class ChallengeEditor extends Component {
)}
{!isTask && (
-
-
this.onUpdateOthers({
- field: 'startDate',
- value: newValue.format()
- })}
- readOnly={false}
- />
-
+ <>
+ {
+ phases.map((phase, index) => (
+ {
+ this.onUpdatePhaseDate(item, index)
+ }}
+ />
+ )
+ )
+ }
+ >
)}
{
this.state.isDeleteLaunch && !this.state.isConfirm && (
diff --git a/src/components/DurationInput/DurationInput.module.scss b/src/components/DurationInput/DurationInput.module.scss
new file mode 100644
index 00000000..c6a1298c
--- /dev/null
+++ b/src/components/DurationInput/DurationInput.module.scss
@@ -0,0 +1,18 @@
+@import "../../styles/includes";
+
+.durationInput {
+ &:disabled {
+ cursor: not-allowed !important;
+ background-color: $inactive !important;
+ }
+
+ &::-webkit-outer-spin-button,
+ &::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+
+ &[type=number] {
+ -moz-appearance: textfield;
+ }
+}
\ No newline at end of file
diff --git a/src/components/DurationInput/index.js b/src/components/DurationInput/index.js
new file mode 100644
index 00000000..84a38228
--- /dev/null
+++ b/src/components/DurationInput/index.js
@@ -0,0 +1,43 @@
+import React, { useEffect, useRef } from 'react'
+import PropTypes from 'prop-types'
+import styles from './DurationInput.module.scss'
+
+const DurationInput = ({ duration, onDurationChange, index, isActive }) => {
+ const inputRef = useRef(null)
+
+ useEffect(() => {
+ document.getElementById(`duration-${index}`).disabled = !isActive
+ }, [isActive, index])
+
+ return (
+
+ {
+ e.preventDefault()
+ onDurationChange(e.target.value)
+ }}
+ onBlur={e => {
+ e.preventDefault()
+ onDurationChange(e.target.value, true)
+ }}
+ autoFocus={inputRef.current === document.activeElement}
+ />
+
+ )
+}
+
+DurationInput.propTypes = {
+ duration: PropTypes.number,
+ onDurationChange: PropTypes.func.isRequired,
+ index: PropTypes.number.isRequired,
+ isActive: PropTypes.bool.isRequired
+}
+
+export default DurationInput
diff --git a/src/components/PhaseInput/PhaseInput.module.scss b/src/components/PhaseInput/PhaseInput.module.scss
index 8ec34a00..12ea4720 100644
--- a/src/components/PhaseInput/PhaseInput.module.scss
+++ b/src/components/PhaseInput/PhaseInput.module.scss
@@ -1,18 +1,33 @@
@import "../../styles/includes";
-@i
+
+:global {
+ div[data-reach-popover] {
+ z-index: 10;
+ }
+}
.container {
display: flex;
+ margin-bottom: 10px;
}
.row {
box-sizing: border-box;
display: flex;
flex-direction: row;
- margin: 30px 30px 0 30px;
+ margin: 20px 30px 0 30px;
align-content: space-between;
justify-content: flex-start;
+ .title {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ margin-right: 10px;
+ font-size: 14px;
+ font-weight: 300;
+ }
+
.field {
@include upto-sm {
display: block;
@@ -42,6 +57,7 @@
&.phaseName {
flex-direction: column;
align-items: flex-start;
+ font-weight: bold;
.previewDates {
font-size: 13px;
@@ -68,7 +84,7 @@
}
.dayPicker {
- width: 180px;
+ width: 200px;
margin-right: 30px;
:global {
@@ -85,6 +101,15 @@
}
}
+ .inputField {
+ margin-right: 30px;
+ width: 80px;
+
+ input {
+ padding: 0 0 0 10px;
+ }
+ }
+
.timePicker {
width: 90px;
@@ -121,4 +146,9 @@
color: black;
}
-
+.dateTimeInput {
+ &:disabled {
+ cursor: not-allowed !important;
+ background-color: $inactive !important;
+ }
+}
diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js
index 2a87c6a6..aeaa8619 100644
--- a/src/components/PhaseInput/index.js
+++ b/src/components/PhaseInput/index.js
@@ -1,112 +1,112 @@
-import _ from 'lodash'
import moment from 'moment'
-import React, { Component } from 'react'
+import React from 'react'
import PropTypes from 'prop-types'
import styles from './PhaseInput.module.scss'
import cn from 'classnames'
-import DayPickerInput from 'react-day-picker/DayPickerInput'
-import TimePicker from 'rc-time-picker'
-import {
- formatDate,
- parseDate
-} from 'react-day-picker/moment'
import 'react-day-picker/lib/style.css'
import 'rc-time-picker/assets/index.css'
-import Select from '../Select'
+import DateTime from '@nateradebaugh/react-datetime'
+import isAfter from 'date-fns/isAfter'
+import subDays from 'date-fns/subDays'
+import '@nateradebaugh/react-datetime/scss/styles.scss'
+import DurationInput from '../DurationInput'
-const timeFormat = 'HH:mm'
-const dateFormat = 'MM/DD/YYYY'
+const dateFormat = 'MM/DD/YYYY HH:mm'
+const inputDateFormat = 'MM/dd/yyyy'
+const inputTimeFormat = 'HH:mm'
+const MAX_LENGTH = 5
-class PhaseInput extends Component {
- render () {
- const { phase, onUpdateSelect, onUpdatePhase, withDates, withDuration, endDate, readOnly } = this.props
- if (_.isEmpty(phase)) return null
- const date = moment(phase.date).format(dateFormat)
- const time = moment(phase.date)
+const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => {
+ const { scheduledStartDate: startDate, scheduledEndDate: endDate, duration, isStartTimeActive, isDurationActive } = phase
- return (
-
-
-
-
- {
- withDuration && endDate && (
-
- Ends:
- {moment(endDate).format(`${dateFormat} ${timeFormat}`)}
-
- )
- }
-
-
+ const getEndDate = (startDate, duration) => moment(startDate).add(duration, 'hours').format(dateFormat)
+
+ const onStartDateChange = (e) => {
+ let startDate = moment(e).format(dateFormat)
+ let endDate = getEndDate(startDate, duration)
+ onUpdatePhase({
+ startDate,
+ endDate,
+ duration
+ })
+ }
+
+ const onDurationChange = (e, isBlur = false) => {
+ if (e.length > MAX_LENGTH) return null
+
+ let duration = parseInt(e || 0)
+ let endDate = getEndDate(startDate, duration)
+ onUpdatePhase({
+ startDate,
+ endDate,
+ duration,
+ isBlur
+ })
+ }
+
+ return (
+
+
+
+
+
+
+
Start Date:
+
{
- withDates && (
-
- {readOnly ? (
- {date}
- ) : ( onUpdatePhase(moment(`${moment(selectedDay).format(dateFormat)} ${time.format(timeFormat)}`, `${dateFormat} ${timeFormat}`))} format={dateFormat} />)}
-
+ readOnly || !isStartTimeActive ? (
+
{moment(startDate).format(dateFormat)}
)
- }
- {
- withDates && (
-
- {readOnly ? (
- {time.format(timeFormat)}
- ) : ( onUpdatePhase(value)}
+ : (
+ {
+ const yesterday = subDays(new Date(), 1)
+ return isAfter(current, yesterday)
+ }}
+ dateFormat={inputDateFormat}
+ timeFormat={inputTimeFormat}
/>)}
-
- )
- }
- {
- withDuration && (
-
- {readOnly ? (
- {phase.duration}
- ) : ( onUpdatePhase(e.target.value)} min={1} placeholder='Duration (hours)' />)}
-
- )
- }
- {
- !_.isEmpty(phase.scorecards) && (
-
-
- )
- }
+
+
+
+
End Date:
+
+ {moment(endDate).format(dateFormat)}
+
+
+
+
Duration:
+
+ {readOnly ? (
+ {duration}
+ ) : (
+
+ )}
- )
- }
+
+ )
}
PhaseInput.defaultProps = {
- withDates: false,
- withDuration: false,
endDate: null,
readOnly: false
}
PhaseInput.propTypes = {
phase: PropTypes.shape().isRequired,
- onUpdateSelect: PropTypes.func,
- onUpdatePhase: PropTypes.func.isRequired,
- withDates: PropTypes.bool,
- withDuration: PropTypes.bool,
- endDate: PropTypes.shape(),
- readOnly: PropTypes.bool
+ onUpdatePhase: PropTypes.func,
+ readOnly: PropTypes.bool,
+ phaseIndex: PropTypes.number.isRequired
}
export default PhaseInput
diff --git a/src/components/StartDateInput/StartDateInput.module.scss b/src/components/StartDateInput/StartDateInput.module.scss
new file mode 100644
index 00000000..e16fc96b
--- /dev/null
+++ b/src/components/StartDateInput/StartDateInput.module.scss
@@ -0,0 +1,129 @@
+@import "../../styles/includes";
+
+:global {
+ div[data-reach-popover] {
+ z-index: 10;
+ }
+}
+
+.container {
+ display: flex;
+}
+
+.row {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: row;
+ margin: 30px 30px 0 30px;
+ align-content: space-between;
+ justify-content: flex-start;
+
+ .field {
+ @include upto-sm {
+ display: block;
+ padding-bottom: 10px;
+ }
+
+ label {
+ @include roboto-bold();
+
+ font-size: 16px;
+ line-height: 19px;
+ font-weight: 500;
+ color: $tc-gray-80;
+ }
+
+ &.col1 {
+ max-width: 185px;
+ min-width: 185px;
+ margin-right: 14px;
+ margin-bottom: auto;
+ margin-top: auto;
+ white-space: nowrap;
+ display: flex;
+ align-items: center;
+ }
+
+ &.phaseName {
+ flex-direction: column;
+ align-items: flex-start;
+
+ .previewDates {
+ font-size: 13px;
+ display: flex;
+ color: $red;
+ font-weight: 300;
+
+ span {
+ color: $dark-gray;
+ margin-right: 10px;
+ }
+ }
+ }
+
+ &.col2 {
+ align-self: flex-end;
+ margin-bottom: auto;
+ margin-top: auto;
+ display: flex;
+ flex-direction: row;
+
+ input {
+ border-radius: 2px;
+ }
+
+ .dayPicker {
+ width: 180px;
+ margin-right: 30px;
+
+ :global {
+ .DayPickerInput {
+ width: 100%;
+ }
+ .DayPickerInput-OverlayWrapper {
+ z-index: 10;
+ }
+ }
+
+ input {
+ width: 100%;
+ }
+ }
+
+ .timePicker {
+ width: 90px;
+
+ input {
+ width: 100%;
+ }
+
+ :global {
+ .rc-time-picker-clear {
+ top: 9px;
+ }
+ }
+ }
+
+ .durationPicker {
+ width: 180px;
+ }
+
+ .scorecards {
+ width: 190px;
+ margin-left: 30px;
+ }
+
+ :global{
+ .rc-time-picker-clear{
+ display: none;
+ }
+ }
+ }
+ }
+}
+
+.readOnlyValue {
+ color: black;
+}
+
+
diff --git a/src/components/StartDateInput/index.js b/src/components/StartDateInput/index.js
new file mode 100644
index 00000000..82c3d6fd
--- /dev/null
+++ b/src/components/StartDateInput/index.js
@@ -0,0 +1,141 @@
+import _ from 'lodash'
+import moment from 'moment-timezone'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import styles from './StartDateInput.module.scss'
+import cn from 'classnames'
+import 'react-day-picker/lib/style.css'
+import 'rc-time-picker/assets/index.css'
+import Select from '../Select'
+import DateTime from '@nateradebaugh/react-datetime'
+import isAfter from 'date-fns/isAfter'
+import subDays from 'date-fns/subDays'
+import '@nateradebaugh/react-datetime/scss/styles.scss'
+
+const dateFormat = 'MM/DD/YYYY HH:mm'
+// const tcTimeZone = 'America/New_York'
+
+class StartDateInput extends Component {
+ constructor (props) {
+ super(props)
+
+ this.onDateChange = this.onDateChange.bind(this)
+ }
+
+ onDateChange (e) {
+ const { onUpdatePhase } = this.props
+
+ onUpdatePhase(moment(e, dateFormat))
+ }
+
+ render () {
+ const { phase, onUpdateSelect, onUpdatePhase, withDates, withDuration, endDate, readOnly } = this.props
+ if (_.isEmpty(phase)) return null
+
+ const date = moment(phase.date).format(dateFormat)
+
+ return (
+
+
+
+
+ {
+ withDuration && endDate && (
+
+ Ends:
+ {moment(endDate).local().format(`${dateFormat}`)}
+
+ )
+ }
+
+
+ {
+ withDates && (
+
+ {
+ readOnly ? (
+ {date}
+ )
+ : (
+ {
+ const yesterday = subDays(new Date(), 1)
+ return isAfter(current, yesterday)
+ }}
+ />)}
+
+ )
+ }
+ {/* {
+ withDates && (
+
+ {readOnly ? (
+ {date}
+ ) : ( onUpdatePhase(moment(`${moment(selectedDay).format(dateFormat)} ${time.format(timeFormat)}`, `${dateFormat} ${timeFormat}`))} format={dateFormat} />)}
+
+ )
+ }
+ {
+ withDates && (
+
+ {readOnly ? (
+ {time.format(timeFormat)}
+ ) : ( onUpdatePhase(value)}
+ />)}
+
+ )
+ } */}
+ {
+ withDuration && (
+
+ {readOnly ? (
+ {phase.duration}
+ ) : ( onUpdatePhase(e.target.value)} min={1} placeholder='Duration (hours)' />)}
+
+ )
+ }
+ {
+ !_.isEmpty(phase.scorecards) && (
+
+
+ )
+ }
+
+
+
+ )
+ }
+}
+
+StartDateInput.defaultProps = {
+ withDates: false,
+ withDuration: false,
+ endDate: null,
+ readOnly: false
+}
+
+StartDateInput.propTypes = {
+ phase: PropTypes.shape().isRequired,
+ onUpdateSelect: PropTypes.func,
+ onUpdatePhase: PropTypes.func.isRequired,
+ withDates: PropTypes.bool,
+ withDuration: PropTypes.bool,
+ endDate: PropTypes.shape(),
+ readOnly: PropTypes.bool
+}
+export default StartDateInput
diff --git a/src/util/date.js b/src/util/date.js
index b5017f29..32e2733c 100644
--- a/src/util/date.js
+++ b/src/util/date.js
@@ -109,6 +109,11 @@ export const updateChallengePhaseBeforeSendRequest = (challengeDetail) => {
const hourToSecond = 60 * 60
if (challengeDetail.phases) {
const challengeDetailTmp = _.cloneDeep(challengeDetail)
+ challengeDetailTmp.startDate = moment(challengeDetail.phases[0].scheduledStartDate)
+ // challengeDetailTmp.registrationStartDate = moment(challengeDetail.phases[0].scheduledStartDate)
+ // challengeDetailTmp.registrationEndDate = moment(challengeDetail.phases[0].scheduledEndDate)
+ // challengeDetailTmp.submissionStartDate = moment(challengeDetail.phases[1].scheduledStartDate)
+ // challengeDetailTmp.submissionEndDate = moment(challengeDetail.phases[1].scheduledEndDate)
challengeDetailTmp.phases = challengeDetailTmp.phases.map((p) => ({
duration: p.duration * hourToSecond,
phaseId: p.phaseId