Skip to content

Commit a4d6360

Browse files
Add an option to select a timeline template
1 parent a91f419 commit a4d6360

File tree

3 files changed

+177
-0
lines changed

3 files changed

+177
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
@import "../../../styles/includes";
2+
3+
.row {
4+
box-sizing: border-box;
5+
display: flex;
6+
flex-direction: row;
7+
margin: 30px 30px 0 30px;
8+
align-content: space-between;
9+
justify-content: flex-start;
10+
11+
.field {
12+
@include upto-sm {
13+
display: block;
14+
padding-bottom: 10px;
15+
}
16+
17+
label {
18+
@include roboto-bold();
19+
20+
font-size: 16px;
21+
line-height: 19px;
22+
font-weight: 500;
23+
color: $tc-gray-80;
24+
}
25+
26+
&.col1 {
27+
max-width: 185px;
28+
min-width: 185px;
29+
margin-right: 14px;
30+
white-space: nowrap;
31+
display: flex;
32+
align-items: center;
33+
flex-grow: 1;
34+
35+
span {
36+
color: $tc-red;
37+
}
38+
}
39+
40+
&.col2.error {
41+
color: $tc-red;
42+
margin-top: -25px;
43+
}
44+
&.col2 {
45+
align-self: flex-end;
46+
width: 80%;
47+
margin-bottom: auto;
48+
margin-top: auto;
49+
display: flex;
50+
flex-direction: row;
51+
max-width: 600px;
52+
min-width: 600px;
53+
}
54+
}
55+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import _ from 'lodash'
2+
import React, { Component } from 'react'
3+
import PropTypes from 'prop-types'
4+
import Select from '../../Select'
5+
import cn from 'classnames'
6+
import styles from './TimelineTemplate-Field.module.scss'
7+
8+
class TimelineTemplateField extends Component {
9+
constructor (props) {
10+
super(props)
11+
this.state = {
12+
validOptions: [],
13+
selectedOption: {}
14+
}
15+
16+
this.checkData = this.checkData.bind(this)
17+
this.loadSelectedOption = this.loadSelectedOption.bind(this)
18+
this.getErrorMessage = this.getErrorMessage.bind(this)
19+
}
20+
21+
componentDidMount () {
22+
this.checkData()
23+
}
24+
25+
shouldComponentUpdate (nextProps) {
26+
const { onUpdateSelect, challengeTimelines, timelineTemplates, challenge } = nextProps
27+
const hasSelectedTypeAndTrack = !_.isEmpty(challenge.typeId) && !_.isEmpty(challenge.trackId)
28+
if ((hasSelectedTypeAndTrack && this.state.validOptions.length === 0) || this.state.matchString !== `${challenge.typeId}-${challenge.trackId}-${this.state.selectedOption.value}`) {
29+
this.checkData(onUpdateSelect, challengeTimelines, timelineTemplates, challenge)
30+
}
31+
return true
32+
}
33+
34+
loadSelectedOption (validOptions, value) {
35+
if (!value) return
36+
const { timelineTemplates, challenge } = this.props
37+
const selectedOption = {}
38+
const selectedTemplate = _.find(timelineTemplates, t => t.id === (value))
39+
if (!selectedTemplate) return
40+
selectedOption.label = selectedTemplate.name
41+
selectedOption.value = selectedTemplate.id
42+
this.setState({
43+
validOptions,
44+
matchString: `${challenge.typeId}-${challenge.trackId}-${value}`,
45+
selectedOption
46+
})
47+
}
48+
49+
checkData () {
50+
const { onUpdateSelect, challengeTimelines, timelineTemplates, challenge } = this.props
51+
const availableTemplates = _.filter(challengeTimelines, ct => ct.typeId === challenge.typeId && ct.trackId === challenge.trackId)
52+
const availableTemplateIds = availableTemplates.map(tt => tt.timelineTemplateId)
53+
const validOptions = _.filter(timelineTemplates, t => _.includes(availableTemplateIds, t.id))
54+
const defaultValue = _.get(_.find(availableTemplates, t => t.isDefault), 'timelineTemplateId')
55+
if (challenge.timelineTemplateId) {
56+
if (!_.includes(validOptions.map(o => o.id), challenge.timelineTemplateId)) {
57+
onUpdateSelect(defaultValue || '', false, 'timelineTemplateId')
58+
return this.loadSelectedOption(validOptions, defaultValue)
59+
}
60+
} else if (defaultValue) {
61+
onUpdateSelect(defaultValue, false, 'timelineTemplateId')
62+
return this.loadSelectedOption(validOptions, defaultValue)
63+
}
64+
}
65+
66+
getErrorMessage () {
67+
if (!this.props.challenge.typeId || !this.props.challenge.trackId) {
68+
return 'Please select a work type and format to enable this field'
69+
} else if (this.props.challenge.submitTriggered && !this.props.challenge.timelineTemplateId) {
70+
return 'Timeline template is required field'
71+
} else if (this.state.validOptions.length === 0) {
72+
return 'Sorry, there are no available timeline templates for the options you have selected'
73+
}
74+
return null
75+
}
76+
77+
render () {
78+
const error = this.getErrorMessage()
79+
return (
80+
<>
81+
<div className={styles.row}>
82+
<div className={cn(styles.field, styles.col1)}>
83+
<label htmlFor='type'>Timeline Template <span>*</span> :</label>
84+
</div>
85+
<div className={cn(styles.field, styles.col2, { [styles.disabled]: this.state.validOptions.length === 0 })}>
86+
<Select
87+
value={this.state.selectedOption}
88+
name='timelineTemplateId'
89+
options={this.state.validOptions.map(type => ({ label: type.name, value: type.id }))}
90+
placeholder='Timeline Template'
91+
isClearable={false}
92+
onChange={(e) => this.props.onUpdateSelect(e.value, false, 'timelineTemplateId')}
93+
isDisabled={this.state.validOptions.length === 0}
94+
/>
95+
</div>
96+
</div>
97+
{ error && <div className={styles.row}>
98+
<div className={cn(styles.field, styles.col1)} />
99+
<div className={cn(styles.field, styles.col2, styles.error)}>
100+
{error}
101+
</div>
102+
</div> }
103+
</>
104+
)
105+
}
106+
}
107+
108+
TimelineTemplateField.defaultProps = {
109+
challengeTimelines: [],
110+
timelineTemplates: []
111+
}
112+
113+
TimelineTemplateField.propTypes = {
114+
challengeTimelines: PropTypes.arrayOf(PropTypes.shape()).isRequired,
115+
timelineTemplates: PropTypes.arrayOf(PropTypes.shape()).isRequired,
116+
challenge: PropTypes.shape().isRequired,
117+
onUpdateSelect: PropTypes.func.isRequired
118+
}
119+
120+
export default TimelineTemplateField

src/components/ChallengeEditor/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import Tooltip from '../Tooltip'
5252
import UseSchedulingAPIField from './UseSchedulingAPIField'
5353
import { getResourceRoleByName } from '../../util/tc'
5454
import { isBetaMode } from '../../util/cookie'
55+
import TimelineTemplateField from './TimelineTemplate-Field'
5556

5657
const theme = {
5758
container: styles.modalContainer
@@ -1353,6 +1354,7 @@ class ChallengeEditor extends Component {
13531354
<div className={styles.newFormContainer}>
13541355
<TrackField tracks={metadata.challengeTracks} challenge={challenge} onUpdateOthers={this.onUpdateOthers} />
13551356
<TypeField types={metadata.challengeTypes} onUpdateSelect={this.onUpdateSelect} challenge={challenge} />
1357+
<TimelineTemplateField challengeTimelines={metadata.challengeTimelines} timelineTemplates={metadata.timelineTemplates} challenge={challenge} onUpdateSelect={this.onUpdateSelect} />
13561358
<ChallengeNameField challenge={challenge} onUpdateInput={this.onUpdateInput} />
13571359
</div>
13581360
{ errorContainer }

0 commit comments

Comments
 (0)