Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ workflows:
- build-dev
filters:
branches:
only: ['dev']
only: ['dev', 'feature/talent_picker_v2']

- deployTest01:
context : org-global
Expand Down
900 changes: 560 additions & 340 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/projects/detail/components/Accordion/Accordion.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const TYPE = {
SLIDER_STANDARD: 'slider-standard',
SELECT_DROPDOWN: 'select-dropdown',
TALENT_PICKER: 'talent-picker',
TALENT_PICKER_V2: 'talent-picker-v2',
}

/**
Expand Down Expand Up @@ -174,6 +175,11 @@ class Accordion extends React.Component {
const totalPeoplePerRole = _.mapValues(_.groupBy(value, v => v.role), valuesUnderGroup => _.sumBy(valuesUnderGroup, v => Number(v.people)))
return _.toPairs(totalPeoplePerRole).filter(([, people]) => people > 0).map(([role, people]) => `${getRoleName(role)}: ${people}`).join(', ')
}
case TYPE.TALENT_PICKER_V2: {
const getRoleName = (role) => _.find(options, { role }).roleTitle
const totalPeoplePerRole = _.mapValues(_.groupBy(value, v => v.role), valuesUnderGroup => _.sumBy(valuesUnderGroup, v => Number(v.people)))
return _.toPairs(totalPeoplePerRole).filter(([, people]) => people > 0).map(([role, people]) => `${getRoleName(role)}: ${people}`).join(', ')
}
default: return value
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@
border-color: $tc-gray-20;
max-width: 300px;
margin-left: 0px;

&.error {
border: 1px solid #ff5b52 !important;
}

&:after {
border-bottom-color: $tc-gray-70;
Expand All @@ -190,6 +194,10 @@
line-height: 24px;
}

textarea.job-textarea {
min-height: 100px;
}

.radio-group-input,
.checkbox-group-item {
width: 100%;
Expand Down
9 changes: 8 additions & 1 deletion src/projects/detail/components/SpecQuestions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import SpecQuestionList from './SpecQuestionList/SpecQuestionList'
import SpecQuestionIcons from './SpecQuestionList/SpecQuestionIcons'
import SkillsQuestion from './SkillsQuestion/SkillsQuestion'
import TalentPickerQuestion from './TalentPickerQuestion/TalentPickerQuestion'
import TalentPickerQuestionV2 from './TalentPickerQuestion/TalentPickerQuestionV2'
import SpecFeatureQuestion from './SpecFeatureQuestion'
import ColorSelector from './../../../components/ColorSelector/ColorSelector'
import SelectDropdown from './../../../components/SelectDropdown/SelectDropdown'
Expand Down Expand Up @@ -382,6 +383,12 @@ class SpecQuestions extends React.Component {
options: q.options,
})
break
case 'talent-picker-v2':
ChildElem = TalentPickerQuestionV2
_.assign(elemProps, {
options: q.options,
})
break
default:
ChildElem = () => (
<div style={{ borderWidth: 1, borderStyle: 'dashed', borderColor: '#f00' }}>
Expand Down Expand Up @@ -452,7 +459,7 @@ class SpecQuestions extends React.Component {
!(question.type === 'estimation' && template.hideEstimation)
).map((q, index) => {
return (
_.includes(['checkbox', 'checkbox-group', 'radio-group', 'add-ons', 'textinput', 'textbox', 'numberinput', 'skills', 'slide-radiogroup', 'slider-standard', 'select-dropdown', 'talent-picker'], q.type) && q.visibilityForRendering === STEP_VISIBILITY.READ_OPTIMIZED ? (
_.includes(['checkbox', 'checkbox-group', 'radio-group', 'add-ons', 'textinput', 'textbox', 'numberinput', 'skills', 'slide-radiogroup', 'slider-standard', 'select-dropdown', 'talent-picker', 'talent-picker-v2'], q.type) && q.visibilityForRendering === STEP_VISIBILITY.READ_OPTIMIZED ? (
<Accordion
key={q.fieldName || `accordion-${index}`}
title={q.summaryTitle || q.title}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import React, { Component, PropTypes } from 'react'
import _ from 'lodash'
import { HOC as hoc } from 'formsy-react'
import cn from 'classnames'

import TalentPickerRowV2 from '../TalentPickerRow/TalentPickerRowV2'
import './TalentPickerQuestion.scss'

class TalentPickerQuestionV2 extends Component {

constructor(props) {
super(props)

this.state = {
options: []
}

this.getDefaultValue = this.getDefaultValue.bind(this)
this.handleValueChange = this.handleValueChange.bind(this)

this.insertRole = this.insertRole.bind(this)
this.removeRole = this.removeRole.bind(this)
this.canDeleteRole = this.canDeleteRole.bind(this)
this.setValidator(props)
}

componentWillReceiveProps(nextProps) {
this.setValidator(nextProps)


if (!_.isEqual(this.props.options, nextProps.options)) {
this.updateOptions(nextProps)
}
}

componentDidMount() {
this.updateOptions(this.props)
}

setValidator(props) {
const { setValidations, required } = props
const validations = {
oneRowHaveValidValue: (formValues, value) => {
if (!required) {
return true
}
return _.some(value, (v) => {
return v.people !== '0' && v.duration !== '0' && v.skills.length > 0 && v.workLoad.value !== null && v.jobDescription.length
}) // validation body
},
noPartialFillsExist: (formValues, value) => {
return _.every(value, v => {

const isOneValueFilled = v.people > 0 || v.duration > 0 || (v.skills && v.skills.length) || (v.jobDescription && v.jobDescription.length) || (v.workLoad && v.workLoad.value !== null)
const isAllValuesFilled = v.people > 0 && v.duration > 0 && v.skills && v.skills.length && v.jobDescription.length && v.workLoad.value !== null
// If one value is filled, all values should be filled to make this row valid. Partial fill is not valid
const isRowValid = !isOneValueFilled || isAllValuesFilled
return isRowValid
})
}
}
setValidations(validations)
}

updateOptions(props) {
const options = props.options.map(o => ({...o, skillsCategories: o.skillsCategory ? [ o.skillsCategory ] : null}))
this.setState({ options })
}

getDefaultValue() {
const { options } = this.props
return options.map((o) => ({
role: o.role,
people: '0',
duration: '0',
skills: [],
additionalSkills: [],
workLoad: { value: null, title: 'Select Workload'},
jobDescription: ''
}))
}

onChange(value) {
const {setValue, name} = this.props

setValue(value)
this.props.onChange(name, value)
}

handleValueChange(index, field, value) {
const { getValue } = this.props
let values = getValue() || this.getDefaultValue()
values = [...values.slice(0, index), { ...values[index], [field]: value }, ...values.slice(index + 1)]

this.onChange(values)
}

insertRole(index, role) {
const { getValue } = this.props
let values = getValue() || this.getDefaultValue()

values = [
...values.slice(0, index),
{
role,
people: '0',
duration: '0',
skills: [],
additionalSkills: [],
workLoad: { value: null, title: 'Select Workload'},
jobDescription: '',
},
...values.slice(index)
]
this.onChange(values)
}

removeRole(index) {
const { getValue } = this.props
let values = getValue() || this.getDefaultValue()
values = [...values.slice(0, index), ...values.slice(index + 1)]
this.onChange(values)
}

canDeleteRole(role, index) {
const { getValue } = this.props
const values = getValue() || this.getDefaultValue()
return _.findIndex(values, { role }) !== index
}

render() {
const { wrapperClass, getValue } = this.props
const { options } = this.state

const errorMessage =
this.props.getErrorMessage() || this.props.validationError
const hasError = !this.props.isPristine() && !this.props.isValid()

const values = getValue() || this.getDefaultValue()

return (
<div className={cn(wrapperClass)}>
<div styleName="container">
{options.length > 0 ? values.map((v, roleIndex) => {
const roleSetting = _.find(options, { role: v.role })
return (
<TalentPickerRowV2
key={roleIndex}
rowIndex={roleIndex}
value={v}
canBeDeleted={this.canDeleteRole}
roleSetting={roleSetting}
onChange={this.handleValueChange}
onDeleteRow={this.removeRole}
onAddRow={this.insertRole}
/>
)
}) : null}
</div>
{hasError ? <p className="error-message">{errorMessage}</p> : null}
</div>
)
}
}

TalentPickerQuestionV2.PropTypes = {
options: PropTypes.arrayOf(
PropTypes.shape({
role: PropTypes.string.isRequired,
skillsCategory: PropTypes.string.isRequired,
roleTitle: PropTypes.string.isRequired,
disabled: PropTypes.bool,
})
).isRequired,
onChange: PropTypes.func,
}

TalentPickerQuestionV2.defaultProps = {
onChange: _.noop
}

export default hoc(TalentPickerQuestionV2)
Loading