From 0afa5a63f9bea836cc809a0a09e61c869822e123 Mon Sep 17 00:00:00 2001 From: Nursoltan Saipolda Date: Sat, 5 Mar 2022 19:39:43 +0800 Subject: [PATCH] add phases --- .../ChallengeSchedule-Field/index.js | 6 +- .../ChallengeEditor/ChallengeView/index.js | 15 +- src/components/ChallengeEditor/index.js | 46 ++-- .../PhaseInput/PhaseInput.module.scss | 24 ++- src/components/PhaseInput/index.js | 201 +++++++++--------- .../StartDateInput/StartDateInput.module.scss | 129 +++++++++++ src/components/StartDateInput/index.js | 141 ++++++++++++ src/util/date.js | 4 +- 8 files changed, 438 insertions(+), 128 deletions(-) create mode 100644 src/components/StartDateInput/StartDateInput.module.scss create mode 100644 src/components/StartDateInput/index.js 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 +190,13 @@ const ChallengeView = ({ )} { -
+ phases.map((phase) => ( -
+ )) } {showTimeline && ( )} {!isTask && ( -
- this.onUpdateOthers({ - field: 'startDate', - value: newValue.format() - })} - readOnly={false} - /> -
+ <> + { + phases.map((phase, index) => ( + { + if ((item.startDate && !moment(item.startDate).isSame(phase.scheduledStartDate)) || + (item.endDate && !moment(item.endDate).isSame(phase.scheduledEndDate)) + ) { + this.onUpdatePhaseDate(item, index) + } + }} + /> + )) + } + )} { this.state.isDeleteLaunch && !this.state.isConfirm && ( diff --git a/src/components/PhaseInput/PhaseInput.module.scss b/src/components/PhaseInput/PhaseInput.module.scss index e16fc96b..c1cf43f1 100644 --- a/src/components/PhaseInput/PhaseInput.module.scss +++ b/src/components/PhaseInput/PhaseInput.module.scss @@ -8,16 +8,26 @@ .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; @@ -47,6 +57,7 @@ &.phaseName { flex-direction: column; align-items: flex-start; + font-weight: bold; .previewDates { font-size: 13px; @@ -73,7 +84,7 @@ } .dayPicker { - width: 180px; + width: 200px; margin-right: 30px; :global { @@ -90,6 +101,15 @@ } } + .inputField { + margin-right: 30px; + width: 80px; + + input { + padding: 0 0 0 10px; + } + } + .timePicker { width: 90px; diff --git a/src/components/PhaseInput/index.js b/src/components/PhaseInput/index.js index c70afdbb..2338d5d3 100644 --- a/src/components/PhaseInput/index.js +++ b/src/components/PhaseInput/index.js @@ -1,141 +1,146 @@ -import _ from 'lodash' -import moment from 'moment-timezone' -import React, { Component } from 'react' +import moment from 'moment' +import React, { useEffect, useState } from 'react' import PropTypes from 'prop-types' import styles from './PhaseInput.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' +const MAX_LENGTH = 5 -class PhaseInput extends Component { - constructor (props) { - super(props) +const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => { + const [startDate, setStartDate] = useState() + const [endDate, setEndDate] = useState() + const [duration, setDuration] = useState() - this.onDateChange = this.onDateChange.bind(this) + useEffect(() => { + if (phase) { + setStartDate(phase.scheduledStartDate) + setEndDate(phase.scheduledEndDate) + setDuration(moment(phase.scheduledEndDate).diff(phase.scheduledStartDate, 'seconds')) + } + }, []) + + useEffect(() => { + if (!readOnly) { + onUpdatePhase({ + startDate, + endDate, + duration + }) + } + }, [startDate, endDate, duration]) + + const onStartDateChange = (e) => { + const start = moment(e).format() + let end = moment(endDate).format() + + if (moment(end).isBefore(moment(start))) { + end = moment(e).add(1, 'day').format(dateFormat) + setEndDate(moment(end).format(dateFormat)) + } + + setStartDate(moment(e).format(dateFormat)) + setDuration(moment(end).diff(start, 'seconds')) } - onDateChange (e) { - const { onUpdatePhase } = this.props + const onEndDateChange = (e) => { + const end = moment(e).format() + const start = moment(startDate).format() + + if (moment(end).isBefore(moment(start))) { + return null + } - onUpdatePhase(moment(e, dateFormat)) + setEndDate(moment(e).format(dateFormat)) + setDuration(moment(end).diff(start, 'seconds')) } - render () { - const { phase, onUpdateSelect, onUpdatePhase, withDates, withDuration, endDate, readOnly } = this.props - if (_.isEmpty(phase)) return null + const onDurationChange = (e) => { + if (e.target.value.length > MAX_LENGTH) return null - const date = moment(phase.date).format(dateFormat) + const dur = parseInt(e.target.value || 0) + setDuration(dur) + const end = moment(startDate).add(duration, 'seconds') + setEndDate(moment(end).format(dateFormat)) + } - return ( -
-
-
- + return ( +
+
+
+ +
+
+ Start Date: +
{ - withDuration && endDate && ( -
- Ends: - {moment(endDate).local().format(`${dateFormat}`)} -
+ readOnly ? ( + {moment(startDate).format(dateFormat)} ) - } + : ( + { + const yesterday = subDays(new Date(), 1) + return isAfter(current, yesterday) + }} + />)}
-
+
+
+ End Date: +
{ - withDates && ( -
- { - readOnly ? ( - {date} - ) - : ( - { - const yesterday = subDays(new Date(), 1) - return isAfter(current, yesterday) - }} - />)} -
+ readOnly ? ( + {moment(endDate).format(dateFormat)} ) - } - {/* { - withDates && ( -
- {readOnly ? ( - {date} - ) : ( onUpdatePhase(moment(`${moment(selectedDay).format(dateFormat)} ${time.format(timeFormat)}`, `${dateFormat} ${timeFormat}`))} format={dateFormat} />)} -
- ) - } - { - withDates && ( -
- {readOnly ? ( - {time.format(timeFormat)} - ) : ( onUpdatePhase(value)} + : ( + { + return isAfter(current, new Date(startDate)) + }} />)} -
- ) - } */} - { - withDuration && ( -
- {readOnly ? ( - {phase.duration} - ) : ( onUpdatePhase(e.target.value)} min={1} placeholder='Duration (hours)' />)} -
- ) - } +
+
+
+ Duration: +
{ - !_.isEmpty(phase.scorecards) && ( -
- )}
- ) - } +
+ ) } 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 + readOnly: PropTypes.bool, + phaseIndex: PropTypes.string.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) && ( +
+