Skip to content
Closed
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_version_v2']

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

Large diffs are not rendered by default.

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 Down
1 change: 1 addition & 0 deletions src/projects/detail/components/SpecQuestions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ class SpecQuestions extends React.Component {
ChildElem = TalentPickerQuestion
_.assign(elemProps, {
options: q.options,
talentPickerVersion: q.talentPickerVersion,
})
break
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,61 @@ class TalentPickerQuestion extends Component {
}

setValidator(props) {
const { setValidations, required } = props
const { setValidations, required, TalentPickerVersion } = 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
}) // validation body
if(TalentPickerVersion && TalentPickerVersion === 'v2.0')
return this.oneRowValidConditionV2(value) // validation body
else
return this.oneRowValidCondition(value) // validation body
},
noPartialFillsExist: (formValues, value) => {
return _.every(value, v => {
const isOneValueFilled = v.people > 0 || v.duration > 0 || (v.skills && v.skills.length)
const isAllValuesFilled = v.people > 0 && v.duration > 0 && v.skills && v.skills.length

// 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
})
if(TalentPickerVersion && TalentPickerVersion === 'v2.0')
return this.noPartialFillsConditionV2(value) // validation body
else
return this.noPartialFillsCondition(value) // validation body
}
}
setValidations(validations)
}

oneRowValidConditionV2(value) {
return _.some(value, (v) => {
return v.people !== '0' && v.duration !== '0' && v.skills.length > 0 && v.workLoad.value !== null && v.jobDescription.length
})
}

noPartialFillsConditionV2(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
})
}

oneRowValidCondition(value) {
return _.some(value, (v) => {
return v.people !== '0' && v.duration !== '0' && v.skills.length > 0
})
}

noPartialFillsCondition(value) {
return _.every(value, v => {
const isOneValueFilled = v.people > 0 || v.duration > 0 || (v.skills && v.skills.length)
const isAllValuesFilled = v.people > 0 && v.duration > 0 && v.skills && v.skills.length

// 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
})
}

updateOptions(props) {
const options = props.options.map(o => ({...o, skillsCategories: o.skillsCategory ? [ o.skillsCategory ] : null}))
this.setState({ options })
Expand All @@ -74,6 +105,9 @@ class TalentPickerQuestion extends Component {
people: '0',
duration: '0',
skills: [],
additionalSkills: [],
workLoad: { value: null, title: 'Select Workload'},
jobDescription: ''
}))
}

Expand Down Expand Up @@ -103,6 +137,9 @@ class TalentPickerQuestion extends Component {
people: '0',
duration: '0',
skills: [],
additionalSkills: [],
workLoad: { value: null, title: 'Select Workload'},
jobDescription: '',
},
...values.slice(index)
]
Expand All @@ -123,7 +160,7 @@ class TalentPickerQuestion extends Component {
}

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

const errorMessage =
Expand All @@ -147,6 +184,7 @@ class TalentPickerQuestion extends Component {
onChange={this.handleValueChange}
onDeleteRow={this.removeRole}
onAddRow={this.insertRole}
talentPickerVersion={talentPickerVersion}
/>
)
}) : null}
Expand Down
112 changes: 109 additions & 3 deletions src/projects/detail/components/TalentPickerRow/TalentPickerRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import IconAdd from '../../../../assets/icons/ui-16px-1_bold-add.svg'
import SkillsQuestion from '../SkillsQuestion/SkillsQuestionBase'
import PositiveNumberInput from '../../../../components/PositiveNumberInput/PositiveNumberInput'
import ProductTypeIcon from '../../../../components/ProductTypeIcon'
import SelectDropdown from 'appirio-tech-react-components/components/SelectDropdown/SelectDropdown'

import styles from './TalentPickerRow.scss'

Expand All @@ -21,12 +22,21 @@ class TalentPickerRow extends React.PureComponent {
this.handlePeopleChange = this.handlePeopleChange.bind(this)
this.handleDurationChange = this.handleDurationChange.bind(this)
this.handleSkillChange = this.handleSkillChange.bind(this)
this.handleWorkloadChange = this.handleWorkloadChange.bind(this)
this.handleJobDescriptionChange = this.handleJobDescriptionChange.bind(this)
this.handleAdditionalSkillChange = this.handleAdditionalSkillChange.bind(this)

this.resetPeople = this.resetPeople.bind(this)
this.resetDuration = this.resetDuration.bind(this)

this.onAddRow = this.onAddRow.bind(this)
this.onDeleteRow = this.onDeleteRow.bind(this)

this.workloadOptions = [
{ value: null, title: 'Select Workload'},
{ value: 'fulltime', title: 'Full-Time'},
{ value: 'fractional', title: 'Fractional'}
]
}

handlePeopleChange(evt) {
Expand All @@ -41,6 +51,17 @@ class TalentPickerRow extends React.PureComponent {
this.props.onChange(this.props.rowIndex, 'skills', value)
}

handleWorkloadChange(evt) {
this.props.onChange(this.props.rowIndex, 'workLoad', evt)
}

handleJobDescriptionChange(evt) {
this.props.onChange(this.props.rowIndex, 'jobDescription', evt.target.value)
}

handleAdditionalSkillChange(value) {
this.props.onChange(this.props.rowIndex, 'additionalSkills', value)
}
resetDuration() {
const { rowIndex, onChange, value } = this.props
if (!value.duration) {
Expand All @@ -66,8 +87,9 @@ class TalentPickerRow extends React.PureComponent {
}

render() {
const { value, canBeDeleted, roleSetting, rowIndex } = this.props
const { value, canBeDeleted, roleSetting, rowIndex, talentPickerVersion } = this.props
const isRowIncomplete = value.people > 0 || value.duration > 0 || (value.skills && value.skills.length)
|| (value.workLoad && value.workLoad.value !== null) || (value.jobDescription && value.jobDescription.trim() !== '')

/* Different columns are defined here and used in componsing mobile/desktop views below */
const roleColumn = (
Expand Down Expand Up @@ -97,7 +119,7 @@ class TalentPickerRow extends React.PureComponent {
const peopleColumn = (
<div styleName="col col-people">
<label className="tc-label" styleName="label">
People
Number Of People
</label>
<PositiveNumberInput
styleName="noMargin"
Expand Down Expand Up @@ -152,7 +174,66 @@ class TalentPickerRow extends React.PureComponent {
</div>
)

return (
const workLoadColumn = (
<div styleName="col col-duration">
<label className="tc-label" styleName="label">
Workload
</label>
<SelectDropdown
name="workLoad"
value={value.workLoad ? value.workLoad.value : null}
theme={`default ${isRowIncomplete && value.workLoad && value.workLoad.value === null ? 'error' : ''}`}
options={ this.workloadOptions }
onSelect={ this.handleWorkloadChange }
/>
</div>
)

const jobDescriptionColumn = (
<div styleName="col col-duration">
<label className="tc-label" styleName="label">
Job Description
</label>
<div styleName="job-description">
<input
className={`TextInput ${isRowIncomplete && !value.jobDescription ? 'error' : 'empty'}`}
maxLength={100}
onChange={this.handleJobDescriptionChange}
placeholder="Job Description"
type="text"
value={value.jobDescription || ''}
/>
</div>
</div>
)

const additionalSkillSelectionColumn = (
<div styleName="col col-skill-selection">
<label className="tc-label" styleName="label">
Preferred/Good-to-Have Skills
</label>
{/*
Please do not refactor getValue prop's value to a binded function with constant reference.
SkillsQuestion is a pure component. If all the props are constant across renders, SkillsQuestion cannot detect the change in value.skills.
So, it'll break the functionality of the component.
"getValue" prop is left as inline arrow function to trigger re rendering of the SkillsQuestion component whenever the parent rerenders.
*/}
<SkillsQuestion
disabled={false}
isFormDisabled={never}
skillsCategories={roleSetting.skillsCategories}
isPristine={always}
isValid={always}
getErrorMessage={emptyError}
setValue={this.handleAdditionalSkillChange}
getValue={() => value.additionalSkills}
onChange={_.noop}
selectWrapperClass={cn(styles.noMargin)}
/>
</div>
)

const talentPickerV2Layout = (
<div styleName="row">
<div styleName="inner-row">
{roleColumn}
Expand All @@ -161,12 +242,37 @@ class TalentPickerRow extends React.PureComponent {

<div styleName="inner-row">
{peopleColumn}
{workLoadColumn}
</div>
<div styleName="inner-row">
{durationColumn}
{jobDescriptionColumn}
</div>

<div styleName="inner-row">{skillSelectionColumn}</div>
<div styleName="inner-row">{additionalSkillSelectionColumn}</div>
</div>
)

const talentPickerDefaultLayout = (
<div styleName="row">
<div styleName="inner-row">
{roleColumn}
{actionsColumn}
</div>

<div styleName="inner-row">
{peopleColumn}
{durationColumn}
</div>

<div styleName="inner-row">{skillSelectionColumn}</div>
</div>
)

return (
(talentPickerVersion && talentPickerVersion === 'v2.0' ? talentPickerV2Layout : talentPickerDefaultLayout)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@
.label {
display: block;
}

.job-description input,
.TextInput[type="text"] {
&.empty {
color: $tc-gray-30;
}

@include placeholder {
@include roboto;
color: $tc-gray-30;
text-transform: none;
font-style: italic;
font-size: $base-unit*3;
line-height: $base-unit*4;
}
}
}

.col-role-container {
Expand Down