Skip to content

Commit dc42874

Browse files
authored
Merge pull request #1517 from topcoder-platform/develop
PROD - changes to date editing on a project
2 parents a1415bb + a7ffea4 commit dc42874

File tree

9 files changed

+216
-49
lines changed

9 files changed

+216
-49
lines changed

src/components/ChallengeEditor/ChallengeSchedule-Field/index.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import StartDateInput from '../../StartDateInput'
1010
import Chart from 'react-google-charts'
1111
import Select from '../../Select'
1212
import { parseSVG } from '../../../util/svg'
13+
import { getPhaseDurationPercentage, getPhaseEndDateInDate } from '../../../util/date'
1314
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
1415
import { faTrash } from '@fortawesome/free-solid-svg-icons'
1516
import PrimaryButton from '../../Buttons/PrimaryButton'
@@ -98,13 +99,13 @@ class ChallengeScheduleField extends Component {
9899
}
99100
if (!phase.predecessor) {
100101
phase.scheduledStartDate = startDate
101-
phase.scheduledEndDate = moment(startDate).add(phase.duration || 0, 'hours').toDate()
102+
phase.scheduledEndDate = getPhaseEndDateInDate(startDate, phase.duration)
102103
phase.actualStartDate = phase.scheduledStartDate
103104
} else {
104105
const preIndex = _.findIndex(phases, (p) => p.id === phase.predecessor)
105106
// `Invalid phase predecessor: ${phase.predecessor}`
106107
phase.scheduledStartDate = phases[preIndex].scheduledEndDate
107-
phase.scheduledEndDate = moment(phase.scheduledStartDate).add(phase.duration || 0, 'hours').toDate()
108+
phase.scheduledEndDate = getPhaseEndDateInDate(phase.scheduledStartDate, phase.duration)
108109
phase.actualStartDate = phase.scheduledStartDate
109110
}
110111
}
@@ -129,7 +130,6 @@ class ChallengeScheduleField extends Component {
129130
]
130131
)
131132

132-
var hourToMilisecond = 60 * 60 * 1000 // = 1 hour
133133
let cStartDate = challenge.startDate
134134
_.map(allPhases, (p, index) => {
135135
const phase = this.getPhaseTemplate(p)
@@ -155,9 +155,9 @@ class ChallengeScheduleField extends Component {
155155
if (startDate.getTime() > currentTime) {
156156
percentage = 0
157157
} else if (endDate.getTime() > currentTime) {
158-
percentage = Math.round(((currentTime - startDate.getTime()) / (hourToMilisecond * p.duration)) * 100)
158+
percentage = getPhaseDurationPercentage(startDate.getTime(), currentTime, p.duration)
159159
} else {
160-
percentage = Math.round(((endDate.getTime() - startDate.getTime()) / (hourToMilisecond * p.duration)) * 100)
160+
percentage = getPhaseDurationPercentage(startDate.getTime(), endDate.getTime(), p.duration)
161161
}
162162
const predecessorPhase = phase.predecessor ? this.getPhaseTemplate(allPhases.filter(ph => ph.phaseId === phase.predecessor)[0]) : null
163163
timelines.push(
@@ -187,7 +187,7 @@ class ChallengeScheduleField extends Component {
187187
phase={this.getPhaseTemplate(p)}
188188
withDuration
189189
onUpdateSelect={onUpdateSelect}
190-
onUpdatePhase={newValue => onUpdatePhase(parseInt(newValue), 'duration', index)}
190+
onUpdatePhase={newValue => onUpdatePhase(newValue, 'duration', index)}
191191
endDate={moment(p.scheduledEndDate)}
192192
readOnly={readOnly}
193193
/>

src/components/ChallengeEditor/index.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
CHALLENGE_STATUS
3232
} from '../../config/constants'
3333
import { getDomainTypes, getResourceRoleByName, is2RoundsChallenge } from '../../util/tc'
34+
import { getPhaseEndDate } from '../../util/date'
3435
import { PrimaryButton, OutlineButton } from '../Buttons'
3536
import TrackField from './Track-Field'
3637
import TypeField from './Type-Field'
@@ -67,6 +68,7 @@ import { isBetaMode } from '../../util/cookie'
6768
import MilestoneField from './Milestone-Field'
6869
import DiscussionField from './Discussion-Field'
6970
import CheckpointPrizesField from './CheckpointPrizes-Field'
71+
import { canChangeDuration } from '../../util/phase'
7072

7173
const theme = {
7274
container: styles.modalContainer
@@ -842,8 +844,7 @@ class ChallengeEditor extends Component {
842844
const is2RoundDesignChallenge = isDesignChallenge && is2RoundChallenge
843845

844846
for (let index = 0; index < phases.length; ++index) {
845-
newChallenge.phases[index].isDurationActive =
846-
moment(newChallenge.phases[index]['scheduledEndDate']).isAfter()
847+
newChallenge.phases[index].isDurationActive = canChangeDuration(newChallenge.phases[index])
847848
if ((newChallenge.phases[index].name === 'Submission' && !is2RoundDesignChallenge) || newChallenge.phases[index].name === 'Checkpoint Submission') {
848849
newChallenge.phases[index].isStartTimeActive = true
849850
} else {
@@ -867,10 +868,11 @@ class ChallengeEditor extends Component {
867868
phase.duration
868869
])
869870
newChallenge.phases[index]['scheduledStartDate'] = moment(phase.startDate).toISOString()
870-
newChallenge.phases[index]['scheduledEndDate'] =
871-
moment(newChallenge.phases[index]['scheduledStartDate'])
872-
.add(newChallenge.phases[index]['duration'], 'hours')
873-
.format('MM/DD/YYYY HH:mm')
871+
872+
newChallenge.phases[index]['scheduledEndDate'] = getPhaseEndDate(
873+
newChallenge.phases[index]['scheduledStartDate'],
874+
newChallenge.phases[index]['duration']
875+
)
874876
} else {
875877
newChallenge.phases[index]['duration'] = phase.duration
876878
newChallenge.phases[index]['scheduledStartDate'] = moment(phase.startDate).toISOString()
@@ -890,10 +892,10 @@ class ChallengeEditor extends Component {
890892
newChallenge.phases[phaseIndex]['scheduledStartDate'] =
891893
newChallenge.phases[phaseIndex - 1]['scheduledEndDate']
892894
}
893-
newChallenge.phases[phaseIndex]['scheduledEndDate'] =
894-
moment(newChallenge.phases[phaseIndex]['scheduledStartDate'])
895-
.add(newChallenge.phases[phaseIndex]['duration'], 'hours')
896-
.format('MM/DD/YYYY HH:mm')
895+
newChallenge.phases[phaseIndex]['scheduledEndDate'] = getPhaseEndDate(
896+
newChallenge.phases[phaseIndex]['scheduledStartDate'],
897+
newChallenge.phases[phaseIndex]['duration']
898+
)
897899
}
898900
if (!_.isEqual(newChallenge.phases[index], phases[index])) {
899901
this.setState({ isPhaseChange: true })

src/components/DurationInput/DurationInput.module.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
@import "../../styles/includes";
22

3+
.container {
4+
display: flex;
5+
align-items: center;
6+
font-size: 14px;
7+
font-weight: 300;
8+
gap: 10px;
9+
}
10+
311
.durationInput {
12+
flex-shrink: 0;
13+
414
&:disabled {
515
cursor: not-allowed !important;
616
background-color: $inactive !important;

src/components/DurationInput/index.js

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,64 @@
1-
import React, { useEffect, useRef } from 'react'
1+
import React, { useEffect, useRef, useMemo } from 'react'
22
import PropTypes from 'prop-types'
33
import styles from './DurationInput.module.scss'
4+
import { getPhaseHoursMinutes, convertPhaseHoursMinutesToPhaseDuration } from '../../util/date'
45

56
const DurationInput = ({ duration, onDurationChange, index, isActive }) => {
6-
const inputRef = useRef(null)
7+
const inputHoursRef = useRef(null)
8+
const inputMinutesRef = useRef(null)
9+
const durationInHoursMinutes = useMemo(() => getPhaseHoursMinutes(duration), [duration])
710

811
useEffect(() => {
9-
document.getElementById(`duration-${index}`).disabled = !isActive
12+
document.getElementById(`duration-${index}-hours`).disabled = !isActive
13+
document.getElementById(`duration-${index}-minutes`).disabled = !isActive
1014
}, [isActive, index])
1115

16+
const onUpdateDuration = (hours, minutes, isBlur) => {
17+
onDurationChange(convertPhaseHoursMinutesToPhaseDuration({
18+
hours,
19+
minutes
20+
}), isBlur)
21+
}
22+
1223
return (
13-
<div key={`duration-${index}-edit`}>
24+
<div key={`duration-${index}-edit`} className={styles.container}>
25+
<span>Hours:</span>
26+
<input
27+
className={styles.durationInput}
28+
id={`duration-${index}-hours`}
29+
key={`duration-${index}-hours`}
30+
ref={inputHoursRef}
31+
min={1}
32+
type='number'
33+
value={durationInHoursMinutes.hours}
34+
onChange={e => {
35+
e.preventDefault()
36+
onUpdateDuration(parseInt(e.target.value), durationInHoursMinutes.minutes, false)
37+
}}
38+
onBlur={e => {
39+
e.preventDefault()
40+
onUpdateDuration(parseInt(e.target.value), durationInHoursMinutes.minutes, true)
41+
}}
42+
autoFocus={inputHoursRef.current === document.activeElement}
43+
/>
44+
<span>Minutes:</span>
1445
<input
1546
className={styles.durationInput}
16-
id={`duration-${index}`}
17-
key={`duration-${index}`}
18-
ref={inputRef}
47+
id={`duration-${index}-minutes`}
48+
key={`duration-${index}-minutes`}
49+
ref={inputMinutesRef}
1950
min={1}
2051
type='number'
21-
value={duration}
52+
value={durationInHoursMinutes.minutes}
2253
onChange={e => {
2354
e.preventDefault()
24-
onDurationChange(e.target.value)
55+
onUpdateDuration(durationInHoursMinutes.hours, parseInt(e.target.value), false)
2556
}}
2657
onBlur={e => {
2758
e.preventDefault()
28-
onDurationChange(e.target.value, true)
59+
onUpdateDuration(durationInHoursMinutes.hours, parseInt(e.target.value), true)
2960
}}
30-
autoFocus={inputRef.current === document.activeElement}
61+
autoFocus={inputMinutesRef.current === document.activeElement}
3162
/>
3263
</div>
3364
)

src/components/PhaseInput/PhaseInput.module.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,18 @@
142142
}
143143
}
144144

145+
.readOnlyDurationContainer {
146+
display: flex;
147+
align-items: center;
148+
font-size: 14px;
149+
font-weight: 300;
150+
gap: 10px;
151+
}
152+
145153
.readOnlyValue {
146154
color: black;
155+
font-size: 1rem;
156+
font-weight: 400;
147157
}
148158

149159
.dateTimeInput {

src/components/PhaseInput/index.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import moment from 'moment'
2-
import React, { useEffect } from 'react'
2+
import React, { useEffect, useMemo } from 'react'
33
import PropTypes from 'prop-types'
44
import styles from './PhaseInput.module.scss'
55
import cn from 'classnames'
@@ -10,6 +10,7 @@ import isAfter from 'date-fns/isAfter'
1010
import subDays from 'date-fns/subDays'
1111
import '@nateradebaugh/react-datetime/scss/styles.scss'
1212
import DurationInput from '../DurationInput'
13+
import { getPhaseHoursMinutes, getPhaseEndDate } from '../../util/date'
1314

1415
const dateFormat = 'MM/DD/YYYY HH:mm'
1516
const inputDateFormat = 'MM/dd/yyyy'
@@ -19,11 +20,11 @@ const MAX_LENGTH = 5
1920
const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => {
2021
const { scheduledStartDate: startDate, scheduledEndDate: endDate, duration, isStartTimeActive, isDurationActive } = phase
2122

22-
const getEndDate = (startDate, duration) => moment(startDate).add(duration, 'hours').format(dateFormat)
23+
const durationHoursMinutes = useMemo(() => getPhaseHoursMinutes(duration), [duration])
2324

2425
const onStartDateChange = (e) => {
2526
let startDate = moment(e).format(dateFormat)
26-
let endDate = getEndDate(startDate, duration)
27+
let endDate = getPhaseEndDate(startDate, duration)
2728
onUpdatePhase({
2829
startDate,
2930
endDate,
@@ -34,7 +35,7 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => {
3435
useEffect(() => {
3536
if (!startDate && onUpdatePhase) {
3637
let startDate = moment().format(dateFormat)
37-
let endDate = getEndDate(startDate, duration)
38+
let endDate = getPhaseEndDate(startDate, duration)
3839
onUpdatePhase({
3940
startDate,
4041
endDate,
@@ -44,10 +45,10 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => {
4445
}, [startDate])
4546

4647
const onDurationChange = (e, isBlur = false) => {
47-
if (e.length > MAX_LENGTH) return null
48+
if (`${e}`.length > MAX_LENGTH) return null
4849

49-
let duration = parseInt(e || 0)
50-
let endDate = getEndDate(startDate, duration)
50+
let duration = e
51+
let endDate = getPhaseEndDate(startDate, duration)
5152
onUpdatePhase({
5253
startDate,
5354
endDate,
@@ -90,10 +91,15 @@ const PhaseInput = ({ onUpdatePhase, phase, readOnly, phaseIndex }) => {
9091
</div>
9192
</div>
9293
<div className={cn(styles.field, styles.col2)}>
93-
<span className={styles.title}>Duration:</span>
94+
<span className={styles.title}>Duration</span>
9495
<div className={styles.inputField}>
9596
{readOnly ? (
96-
<span className={styles.readOnlyValue}>{duration}</span>
97+
<div className={styles.readOnlyDurationContainer}>
98+
<span>Hours: </span>
99+
<span className={styles.readOnlyValue}>{durationHoursMinutes.hours}</span>
100+
<span>Minutes: </span>
101+
<span className={styles.readOnlyValue}>{durationHoursMinutes.minutes}</span>
102+
</div>
97103
) : (
98104
<DurationInput
99105
duration={duration}

src/components/StartDateInput/index.js

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import DateTime from '@nateradebaugh/react-datetime'
1111
import isAfter from 'date-fns/isAfter'
1212
import subDays from 'date-fns/subDays'
1313
import '@nateradebaugh/react-datetime/scss/styles.scss'
14+
import { getPhaseHoursMinutes, convertPhaseHoursMinutesToPhaseDuration } from '../../util/date'
1415

1516
const dateFormat = 'MM/DD/YYYY HH:mm'
1617
// const tcTimeZone = 'America/New_York'
@@ -92,15 +93,49 @@ class StartDateInput extends Component {
9293
</div>
9394
)
9495
} */}
95-
{
96-
withDuration && (
97-
<div className={styles.durationPicker}>
98-
{readOnly ? (
99-
<span className={styles.readOnlyValue}>{phase.duration}</span>
100-
) : (<input type='number' value={phase.duration} onChange={e => onUpdatePhase(e.target.value)} min={1} placeholder='Duration (hours)' />)}
101-
</div>
102-
)
103-
}
96+
{withDuration && (
97+
<div className={styles.durationPicker}>
98+
{readOnly ? (
99+
<span className={styles.readOnlyValue}>{`hours: ${
100+
getPhaseHoursMinutes(phase.duration).hours
101+
}, minutes: ${
102+
getPhaseHoursMinutes(phase.duration).minutes
103+
}`}</span>
104+
) : (
105+
<div>
106+
<input
107+
type='number'
108+
value={getPhaseHoursMinutes(phase.duration).hours}
109+
onChange={e =>
110+
onUpdatePhase(
111+
convertPhaseHoursMinutesToPhaseDuration({
112+
hours: parseInt(e.target.value),
113+
minutes: getPhaseHoursMinutes(phase.duration)
114+
.minutes
115+
})
116+
)
117+
}
118+
min={1}
119+
placeholder='Duration (hours)'
120+
/>
121+
<input
122+
type='number'
123+
value={getPhaseHoursMinutes(phase.duration).minutes}
124+
onChange={e =>
125+
onUpdatePhase(
126+
convertPhaseHoursMinutesToPhaseDuration({
127+
hours: getPhaseHoursMinutes(phase.duration).hours,
128+
minutes: parseInt(e.target.value)
129+
})
130+
)
131+
}
132+
min={1}
133+
placeholder='Duration (minutes)'
134+
/>
135+
</div>
136+
)}
137+
</div>
138+
)}
104139
{
105140
!_.isEmpty(phase.scorecards) && (
106141
<div className={styles.scorecards}>

0 commit comments

Comments
 (0)