diff --git a/.circleci/config.yml b/.circleci/config.yml
index 897301638..a32f2f31f 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -136,7 +136,7 @@ workflows:
- build-dev
filters:
branches:
- only: ['feature/unified-permissions', 'feature/accept-reject-terms-in-profile']
+ only: ['feature/taas-jobs-2']
- deployProd:
context : org-global
diff --git a/config/constants/dev.js b/config/constants/dev.js
index 930c775a1..4c84dad0a 100644
--- a/config/constants/dev.js
+++ b/config/constants/dev.js
@@ -54,5 +54,7 @@ module.exports = {
DASHBOARD_FAQ_CONTENT_ID : process.env.DASHBOARD_FAQ_CONTENT_ID,
CONTENTFUL_DELIVERY_KEY : process.env.CONTENTFUL_DELIVERY_KEY,
- CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID
+ CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID,
+
+ TAAS_APP_URL: 'https://platform.topcoder-dev.com/taas'
}
diff --git a/config/constants/master.js b/config/constants/master.js
index 6234a4e14..12d635aa3 100644
--- a/config/constants/master.js
+++ b/config/constants/master.js
@@ -54,5 +54,7 @@ module.exports = {
DASHBOARD_FAQ_CONTENT_ID : process.env.DASHBOARD_FAQ_CONTENT_ID,
CONTENTFUL_DELIVERY_KEY : process.env.CONTENTFUL_DELIVERY_KEY,
- CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID
+ CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID,
+
+ TAAS_APP_URL: 'https://platform.topcoder.com/taas'
}
diff --git a/config/constants/qa.js b/config/constants/qa.js
index 3be11e79b..5cf3c2ea9 100644
--- a/config/constants/qa.js
+++ b/config/constants/qa.js
@@ -49,5 +49,7 @@ module.exports = {
TC_SYSTEM_USERID: process.env.QA_TC_SYSTEM_USERID,
MAINTENANCE_MODE: process.env.QA_MAINTENANCE_MODE,
- TC_CDN_URL: process.env.TC_CDN_URL
+ TC_CDN_URL: process.env.TC_CDN_URL,
+
+ TAAS_APP_URL: 'https://platform.topcoder-dev.com/taas'
}
diff --git a/src/api/skills.js b/src/api/skills.js
new file mode 100644
index 000000000..cdb90e2ef
--- /dev/null
+++ b/src/api/skills.js
@@ -0,0 +1,49 @@
+import { TC_API_URL } from '../config/constants'
+import { axiosInstance as axios } from './requestInterceptor'
+
+const skillPageSize = 100
+let cachedSkillsAsPromise
+
+/**
+ * Loads and caches all the skills the first time. Returns the skills list from the cache from the second time.
+ */
+export function getSkills() {
+ cachedSkillsAsPromise = cachedSkillsAsPromise || getAllSkills().catch(ex => {
+ console.error('Error loading skills', ex)
+ cachedSkillsAsPromise = null
+ return []
+ })
+
+ return cachedSkillsAsPromise
+}
+
+/**
+ * Recursively loads all the pages from skills api.
+ */
+function getAllSkills() {
+ let skills = []
+
+ return new Promise((resolve, reject) => {
+ const loop = (page) => getSkillsPage(page)
+ .then((skillResponse) => {
+ skills = skills.concat(skillResponse.data)
+ if (skillResponse.data.length === skillPageSize) {
+ page++
+ loop(page)
+ } else {
+ resolve(skills)
+ }
+ })
+ .catch(ex => reject(ex))
+
+ loop(1)
+ })
+}
+
+/**
+ * Loads the skills in the given page.
+ * @param {number} page The page number to load
+ */
+function getSkillsPage(page) {
+ return axios.get(`${TC_API_URL}/v5/taas-teams/skills?perPage=${skillPageSize}&orderBy=name&page=${page}`)
+}
diff --git a/src/components/Feed/FeedComments.jsx b/src/components/Feed/FeedComments.jsx
index 6cbf2a63d..28a71adec 100644
--- a/src/components/Feed/FeedComments.jsx
+++ b/src/components/Feed/FeedComments.jsx
@@ -176,6 +176,21 @@ class FeedComments extends React.Component {
}
}
+ convertMdToHtml(markdown) {
+ try {
+ return (
+ Topcoder will be contacting you soon to discuss your project proposal.
+
+
+
+In the meantime, get a jump on the process by inviting your coworkers to your project and securely share any detailed requirements documents you have inside your project.
+
+
+ ),
+ leftButton: {
+ header: 'All Projects',
+ subText: 'View all of your projects',
+ url: this.getProjectUrl(isTaas)
+ },
+ rightButton: {
+ header: 'Go to Project',
+ subText: 'Invite your team members and share requirements',
+ url: this.getProjectUrl(isTaas, projectId)
+ },
}
+
+ const taas = {
+ headerSubTitle: 'Your talent request has been created',
+ textBody: (
+
+ Topcoder will be contacting you soon to discuss your talent needs.
+
+ ),
+ leftButton: {
+ header: 'All Projects',
+ subText: 'View all of your projects',
+ url: this.getProjectUrl(false) // also showing link to Connect App Project List
+ },
+ rightButton: {
+ header: 'View Talent Request',
+ subText: 'Modify your request and track fulfillment',
+ url: this.getProjectUrl(isTaas, projectId)
+ },
+ }
+
+ return isTaas? taas: project
}
render() {
+
+ const {
+ headerSubTitle,
+ textBody,
+ leftButton,
+ rightButton
+ } = this.getPageConfiguration()
+
return (
Congratulations!
-
Your project has been created
-
- Topcoder will be contacting you soon to discuss your project proposal.
-
-
-
-In the meantime, get a jump on the process by inviting your coworkers to your project and securely share any detailed requirements documents you have inside your project.
-
-
+
{headerSubTitle}
+ {textBody}
@@ -41,7 +108,6 @@ In the meantime, get a jump on the process by inviting your coworkers to your pr
}
ProjectSubmitted.defaultProps = {
- vm: {},
params: {},
}
diff --git a/src/projects/create/components/ProjectWizard.jsx b/src/projects/create/components/ProjectWizard.jsx
index 0ca419fdc..b05edf01a 100644
--- a/src/projects/create/components/ProjectWizard.jsx
+++ b/src/projects/create/components/ProjectWizard.jsx
@@ -605,9 +605,6 @@ class ProjectWizard extends Component {
/>
diff --git a/src/projects/create/containers/CreateContainer.jsx b/src/projects/create/containers/CreateContainer.jsx
index 0ee63d074..5e687c758 100644
--- a/src/projects/create/containers/CreateContainer.jsx
+++ b/src/projects/create/containers/CreateContainer.jsx
@@ -124,11 +124,12 @@ class CreateContainer extends React.Component {
projectId: nextProjectId,
isProjectDirty: false
}, () => {
+ const type = _.get(this.state, 'updatedProject.type')
// go to submitted state
console.log('go to submitted state')
window.localStorage.removeItem(LS_INCOMPLETE_PROJECT)
window.localStorage.removeItem(LS_INCOMPLETE_WIZARD)
- this.props.history.push('/new-project/submitted/' + nextProjectId)
+ this.props.history.push('/new-project/submitted/' + nextProjectId + (type ? `?type=${type}` : ''))
})
} else if (this.state.creatingProject !== nextProps.processing) {
diff --git a/src/projects/detail/components/Accordion/Accordion.jsx b/src/projects/detail/components/Accordion/Accordion.jsx
index 3b64f6543..3fb3d9c2b 100644
--- a/src/projects/detail/components/Accordion/Accordion.jsx
+++ b/src/projects/detail/components/Accordion/Accordion.jsx
@@ -33,6 +33,7 @@ const TYPE = {
SELECT_DROPDOWN: 'select-dropdown',
TALENT_PICKER: 'talent-picker',
TALENT_PICKER_V2: 'talent-picker-v2',
+ JOBS_PICKER: 'jobs-picker',
}
/**
@@ -180,6 +181,9 @@ 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.JOBS_PICKER: {
+ return _.map(value, 'title').join(', ')
+ }
default: return value
}
}
diff --git a/src/projects/detail/components/JobPickerRow/JobPickerRow.jsx b/src/projects/detail/components/JobPickerRow/JobPickerRow.jsx
new file mode 100644
index 000000000..db9357a17
--- /dev/null
+++ b/src/projects/detail/components/JobPickerRow/JobPickerRow.jsx
@@ -0,0 +1,289 @@
+import React from 'react'
+import PT from 'prop-types'
+import cn from 'classnames'
+
+import IconX from '../../../../assets/icons/ui-16px-1_bold-remove.svg'
+import IconAdd from '../../../../assets/icons/ui-16px-1_bold-add.svg'
+import SkillsQuestion from '../SkillsQuestion/SkillsQuestionBase'
+import PositiveNumberInput from '../../../../components/PositiveNumberInput/PositiveNumberInput'
+import SelectDropdown from 'appirio-tech-react-components/components/SelectDropdown/SelectDropdown'
+
+import styles from './JobPickerRow.scss'
+
+const always = () => true
+const never = () => false
+const emptyError = () => ''
+
+const MAX_NUMBER = Math.pow(2, 31) - 1
+
+class JobPickerRow extends React.PureComponent {
+ constructor(props) {
+ super(props)
+
+ this.handlePeopleChange = this.handlePeopleChange.bind(this)
+ this.handleDurationChange = this.handleDurationChange.bind(this)
+ this.handleSkillChange = this.handleSkillChange.bind(this)
+ this.handleJobTitleChange = this.handleJobTitleChange.bind(this)
+ this.handleRoleChange = this.handleRoleChange.bind(this)
+ this.handleWorkloadChange = this.handleWorkloadChange.bind(this)
+ this.handleDescriptionChange = this.handleDescriptionChange.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'}
+ ]
+
+ this.roleOptions = [
+ { value: null, title: 'Select Role'},
+ { value: 'designer', title: 'Designer'},
+ { value: 'software-developer', title: 'Software Developer'},
+ { value: 'data-scientist', title: 'Data Scientist'},
+ { value: 'data-engineer', title: 'Data Engineer'},
+ { value: 'qa', title: 'QA Tester'},
+ { value: 'qa-engineer', title: 'QA Engineer'}
+ ]
+ }
+ handleJobTitleChange(evt) {
+ this.props.onChange(this.props.rowIndex, 'title', evt.target.value)
+ }
+ handlePeopleChange(evt) {
+ this.props.onChange(this.props.rowIndex, 'people', evt.target.value)
+ }
+
+ handleDurationChange(evt) {
+ this.props.onChange(this.props.rowIndex, 'duration', evt.target.value)
+ }
+
+ handleSkillChange(value) {
+ this.props.onChange(this.props.rowIndex, 'skills', value)
+ }
+
+ handleWorkloadChange(evt) {
+ this.props.onChange(this.props.rowIndex, 'workLoad', evt)
+ }
+
+ handleRoleChange(evt) {
+ this.props.onChange(this.props.rowIndex, 'role', evt)
+ }
+
+ handleDescriptionChange(evt) {
+ this.props.onChange(this.props.rowIndex, 'description', evt.target.value)
+ }
+
+ resetDuration() {
+ const { rowIndex, onChange, value } = this.props
+ if (!value.duration) {
+ onChange(rowIndex, 'duration', '0')
+ }
+ }
+
+ resetPeople() {
+ const { rowIndex, onChange, value } = this.props
+ if (!value.people) {
+ onChange(rowIndex, 'people', '0')
+ }
+ }
+
+ onAddRow() {
+ const { rowIndex, value, onAddRow: addRowHandler } = this.props
+ addRowHandler(rowIndex + 1, value.role)
+ }
+
+ onDeleteRow() {
+ const { rowIndex, onDeleteRow: deleteRowHandler } = this.props
+ deleteRowHandler(rowIndex)
+ }
+
+ render() {
+ const { value, rowIndex } = this.props
+ const isRowIncomplete = value.title.trim().length > 0 || value.people > 0 || value.duration > 0 || (value.skills && value.skills.length)
+ ||(value.role && value.role.value !== null) ||(value.workLoad && value.workLoad.value !== null) || (value.description.trim().length > 0)
+
+ /* Different columns are defined here and used in componsing mobile/desktop views below */
+ const titleColumn = (
+
+
+
+ Job {rowIndex +1}
+
+
+
+
+ )
+
+ const actionsColumn = (
+
+
+
+
+
+ { rowIndex > 0 && (
+
+
+
+ )}
+
+
+ )
+
+ const peopleColumn = (
+
+
+ Number Of People
+
+
+
+ )
+
+ const durationColumn = (
+
+
+ Duration (months)
+
+
+
+ )
+
+ const skillSelectionColumn = (
+
+
+ Skills
+
+ {/*
+ 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.
+ */}
+ value.skills}
+ onChange={_.noop}
+ selectWrapperClass={cn(styles.noMargin, {[styles.skillHasError]: isRowIncomplete && !(value.skills && value.skills.length)})}
+ />
+
+ )
+
+ const workLoadColumn = (
+
+
+ Workload
+
+
+
+ )
+
+ const roleColumn = (
+
+
+ Role
+
+
+
+ )
+ const descriptionColumn = (
+
+
+ Job Description
+
+
+
+
+
+ )
+
+ return (
+
+
+ {titleColumn}
+ {actionsColumn}
+
+
+
+ {peopleColumn}
+ {roleColumn}
+
+
+ {durationColumn}
+ {workLoadColumn}
+
+
+ {descriptionColumn}
+
+
+
{skillSelectionColumn}
+
+ )
+ }
+}
+
+JobPickerRow.propTypes = {
+ rowIndex: PT.number.isRequired,
+ onChange: PT.func.isRequired,
+ onAddRow: PT.func.isRequired,
+ onDeleteRow: PT.func.isRequired,
+ value: PT.shape({
+ people: PT.string.isRequired,
+ title: PT.string,
+ duration: PT.string.isRequired,
+ skills: PT.array,
+ }),
+}
+
+JobPickerRow.defaultProps = {}
+
+export default JobPickerRow
diff --git a/src/projects/detail/components/JobPickerRow/JobPickerRow.scss b/src/projects/detail/components/JobPickerRow/JobPickerRow.scss
new file mode 100644
index 000000000..04143f860
--- /dev/null
+++ b/src/projects/detail/components/JobPickerRow/JobPickerRow.scss
@@ -0,0 +1,146 @@
+@import "~tc-ui/src/styles/tc-includes";
+@import "../../../../styles/includes";
+
+.action-btn {
+ cursor: pointer;
+ margin: 0 2px;
+ background-color: $tc-gray-neutral-dark;
+ height: 23px;
+ width: 23px;
+ text-align: center;
+ line-height: 25px;
+ border-radius: 12px;
+
+ svg {
+ fill: $tc-gray-50;
+ height: 10px;
+ }
+}
+
+.action-btn-remove {
+ margin-left: 8px;
+}
+
+.d-flex {
+ display: flex;
+}
+
+.row {
+ border: 1px solid $tc-gray-neutral-dark;
+ border-top-width: 0;
+ white-space: nowrap;
+ padding: 4px;
+ line-height: 20px;
+ font-size: 13px;
+ vertical-align: top;
+
+ &:first-child {
+ border-top-width: 1px;
+ }
+}
+
+.inner-row {
+ width: 100%;
+ display: flex;
+}
+
+.col {
+ padding: 4px 7.5px;
+ flex-grow: 0;
+ flex-shrink: 0;
+
+ &.col-duration,
+ &.col-people {
+ width: 50%;
+ }
+
+ &.col-skill-selection {
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin-bottom: 4px;
+
+ // Prevents the column from expanding beyond parent due to flex-grow:1
+ min-width: 0;
+ }
+
+ &.col-role {
+ flex-grow: 1;
+
+ // Prevents the column from expanding beyond parent due to flex-grow:1
+ min-width: 0;
+ }
+
+ &.col-actions {
+ width: 76px;
+ }
+
+ // Resets standard margins applied by tc-file-field__inputs elements
+ .noMargin {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ .label {
+ display: block;
+ }
+
+ .job-description textarea,
+ .job-textarea[type="text"] {
+ &.empty {
+ color: $tc-gray-30;
+ }
+ min-height : 90px;
+
+ @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 {
+ display: flex;
+ align-items: center;
+}
+
+.col-job-name-container {
+ display: flex;
+ align-items: center;
+ label {
+ flex: 40px 0 0;
+ }
+}
+
+.col-actions-container {
+ justify-content: flex-end;
+ margin-top: 5px;
+}
+
+.col-role {
+ svg {
+ width: 22.5px;
+ margin-right: 11px;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+}
+
+.role-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+
+.skillHasError {
+ :global {
+ .react-select__control {
+ border-color: $tc-red-70;
+ }
+ }
+}
diff --git a/src/projects/detail/components/JobsPickerQuestion/JobsPickerQuestion.jsx b/src/projects/detail/components/JobsPickerQuestion/JobsPickerQuestion.jsx
new file mode 100644
index 000000000..bdcf09fe5
--- /dev/null
+++ b/src/projects/detail/components/JobsPickerQuestion/JobsPickerQuestion.jsx
@@ -0,0 +1,156 @@
+import React, { Component, PropTypes } from 'react'
+import _ from 'lodash'
+import { HOC as hoc } from 'formsy-react'
+import cn from 'classnames'
+
+import JobPickerRow from '../JobPickerRow/JobPickerRow.jsx'
+import './JobsPickerQuestion.scss'
+
+class JobsPickerQuestion extends Component {
+
+ constructor(props) {
+ super(props)
+
+ this.getDefaultValue = this.getDefaultValue.bind(this)
+ this.handleValueChange = this.handleValueChange.bind(this)
+
+ this.insertJob = this.insertJob.bind(this)
+ this.removeJob = this.removeJob.bind(this)
+ this.setValidator(props)
+
+ const { getValue } = props
+ let values = getValue()
+ if (values) {
+ values = _.map(values, (v) => {
+ return {
+ ...v,
+ // we need unique key for proper rendering of the component list
+ _key: _.uniqueId('job_key_')
+ }
+ })
+ }else {
+ values = this.getDefaultValue()
+ }
+
+ this.state = {
+ values
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setValidator(nextProps)
+ }
+
+ setValidator(props) {
+ const { setValidations, required } = props
+ const validations = {
+ oneRowHaveValidValue: (formValues, value) => {
+ if (!required) {
+ return true
+ }
+ return _.some(value, (v) => {
+ return v.title.trim().length && v.people !== '0' && v.duration !== '0' && v.skills.length > 0 && v.workLoad.value !== null && v.role.value !== null && v.description.trim().length
+ }) // validation body
+ },
+ noPartialFillsExist: (formValues, value) => {
+ return _.every(value, v => {
+ const isAllValuesFilled = v.title.trim().length > 0 && v.people > 0 && v.duration > 0 && v.skills && v.skills.length && v.description.trim().length && v.workLoad.value !== null && v.role.value !== null
+ return isAllValuesFilled
+ })
+ }
+ }
+ setValidations(validations)
+ }
+
+ getDefaultValue() {
+ return [{
+ title: '',
+ role: { value: null, title: 'Select Role'},
+ people: '0',
+ duration: '0',
+ skills: [],
+ workLoad: { value: null, title: 'Select Workload'},
+ description: '',
+ // we need unique key for proper rendering of the component list
+ _key: _.uniqueId('job_key_')
+ }]
+ }
+
+ onChange(values) {
+ const {setValue, name} = this.props
+
+ this.setState({values})
+ // remove unique _key
+ const newValues = _.map(values, (v) => {
+ const cloneV = {...v}
+ delete cloneV['_key']
+ return cloneV
+ })
+ setValue(newValues)
+ this.props.onChange(name, newValues)
+ }
+
+ handleValueChange(index, field, value) {
+ let {values} = this.state
+ const currentValue = values[index]
+ currentValue[field] = value
+ values = [...values.slice(0, index), {...currentValue}, ...values.slice(index + 1)]
+ this.onChange(values)
+ }
+
+ insertJob(index) {
+ let {values} = this.state
+
+ values = [
+ ...values.slice(0, index),
+ ...this.getDefaultValue(),
+ ...values.slice(index)
+ ]
+ this.onChange(values)
+ }
+
+ removeJob(index) {
+ let {values} = this.state
+ values = [...values.slice(0, index), ...values.slice(index + 1)]
+ this.onChange(values)
+ }
+
+ render() {
+ const { wrapperClass } = this.props
+ const errorMessage =
+ this.props.getErrorMessage() || this.props.validationError
+ const hasError = !this.props.isPristine() && !this.props.isValid()
+
+ const {values} = this.state
+
+ return (
+
+
+ {values.map((v, jobIndex) => {
+ return (
+
+ )
+ })}
+
+ {hasError ?
{errorMessage}
: null}
+
+ )
+ }
+}
+
+JobsPickerQuestion.PropTypes = {
+ onChange: PropTypes.func,
+}
+
+JobsPickerQuestion.defaultProps = {
+ onChange: _.noop
+}
+
+export default hoc(JobsPickerQuestion)
diff --git a/src/projects/detail/components/JobsPickerQuestion/JobsPickerQuestion.scss b/src/projects/detail/components/JobsPickerQuestion/JobsPickerQuestion.scss
new file mode 100644
index 000000000..436c84d58
--- /dev/null
+++ b/src/projects/detail/components/JobsPickerQuestion/JobsPickerQuestion.scss
@@ -0,0 +1,8 @@
+@import '~tc-ui/src/styles/tc-includes';
+@import '../../../../styles/includes';
+
+.container {
+ font-size: 15px;
+ text-align: left;
+ margin-top: 5px;
+}
diff --git a/src/projects/detail/components/SkillsQuestion/SkillsQuestionBase.jsx b/src/projects/detail/components/SkillsQuestion/SkillsQuestionBase.jsx
index 56b0a3168..9ed46d17f 100644
--- a/src/projects/detail/components/SkillsQuestion/SkillsQuestionBase.jsx
+++ b/src/projects/detail/components/SkillsQuestion/SkillsQuestionBase.jsx
@@ -3,11 +3,8 @@ import _ from 'lodash'
import SkillsCheckboxGroup from './SkillsCheckboxGroup'
import Select from '../../../../components/Select/Select'
import './SkillsQuestion.scss'
-import { axiosInstance as axios } from '../../../../api/requestInterceptor'
-import { TC_API_URL } from '../../../../config/constants'
import { createFilter } from 'react-select'
-
-let cachedOptions
+import { getSkills } from '../../../../api/skills'
/**
* If `categoriesMapping` is defined - filter options using selected categories.
@@ -21,6 +18,9 @@ let cachedOptions
* @returns {Array} available options
*/
const getAvailableOptions = (categoriesMapping, selectedCategories, skillsCategories, options) => {
+ // NOTE:
+ // Disable filtering skills by categories for now, because V5 Skills API doesn't have categories for now.
+ /*
let mappedCategories
if (categoriesMapping) {
mappedCategories = _.map(selectedCategories, (category) => categoriesMapping[category] ? categoriesMapping[category].toLowerCase() : null)
@@ -31,6 +31,7 @@ const getAvailableOptions = (categoriesMapping, selectedCategories, skillsCatego
if (mappedCategories) {
return options.filter(option => _.intersection((option.categories || []).map(c => c.toLowerCase()), mappedCategories).length > 0)
}
+ */
return options
}
@@ -38,7 +39,7 @@ class SkillsQuestion extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
- options: cachedOptions || [],
+ options: [],
availableOptions: [],
customOptionValue: '',
}
@@ -47,17 +48,13 @@ class SkillsQuestion extends React.PureComponent {
}
componentWillMount() {
- if (!cachedOptions) {
- axios.get(`${TC_API_URL}/v3/tags/?domain=SKILLS&status=APPROVED`)
- .then(resp => {
- const options = _.get(resp.data, 'result.content', {})
-
- cachedOptions = options
- this.updateOptions(options)
- })
- } else {
- this.updateOptions(cachedOptions)
- }
+ getSkills().then(skills => {
+ const options = skills.map(skill => ({
+ skillId: skill.id,
+ name: skill.name
+ }))
+ this.updateOptions(options)
+ })
}
componentWillReceiveProps(nextProps) {
@@ -77,7 +74,7 @@ class SkillsQuestion extends React.PureComponent {
this.setState({ options })
this.updateAvailableOptions(this.props, options)
if (onSkillsLoaded) {
- onSkillsLoaded(options.map((option) => _.pick(option, ['id', 'name'])))
+ onSkillsLoaded(options)
}
}
@@ -93,7 +90,6 @@ class SkillsQuestion extends React.PureComponent {
// if have a mapping for categories, then filter options, otherwise use all options
const availableOptions = getAvailableOptions(categoriesMapping, selectedCategories, skillsCategories, options)
- .map(option => _.pick(option, ['id', 'name']))
this.setState({ availableOptions })
}
@@ -182,17 +178,17 @@ class SkillsQuestion extends React.PureComponent {
const selectedCategories = _.get(currentProjectData, categoriesField, [])
let currentValues = getValue() || []
- // remove from currentValues not available options but still keep created custom options without id
- currentValues = currentValues.filter(skill => _.some(availableOptions, skill) || !skill.id)
+ // remove from currentValues not available options but still keep created custom options without skillId
+ currentValues = currentValues.filter(skill => _.some(availableOptions, skill) || !skill.skillId)
const questionDisabled = isFormDisabled() || disabled || (selectedCategories.length === 0 && _.isUndefined(skillsCategories))
const hasError = !isPristine() && !isValid()
const errorMessage = getErrorMessage() || validationError
- const checkboxGroupOptions = availableOptions.filter(option => frequentSkills.indexOf(option.id) > -1)
- const checkboxGroupValues = currentValues.filter(val => _.some(checkboxGroupOptions, option => option.id === val.id ))
+ const checkboxGroupOptions = availableOptions.filter(option => frequentSkills.indexOf(option.skillId) > -1)
+ const checkboxGroupValues = currentValues.filter(val => _.some(checkboxGroupOptions, option => option.skillId === val.skillId ))
- const selectGroupOptions = availableOptions.filter(option => frequentSkills.indexOf(option.id) === -1)
+ const selectGroupOptions = availableOptions.filter(option => frequentSkills.indexOf(option.skillId) === -1)
if (customOptionValue) {
selectGroupOptions.unshift({ name: customOptionValue })
}
diff --git a/src/projects/detail/components/SpecQuestions.jsx b/src/projects/detail/components/SpecQuestions.jsx
index aa4afd86b..4958acecf 100644
--- a/src/projects/detail/components/SpecQuestions.jsx
+++ b/src/projects/detail/components/SpecQuestions.jsx
@@ -12,6 +12,7 @@ import SpecQuestionIcons from './SpecQuestionList/SpecQuestionIcons'
import SkillsQuestion from './SkillsQuestion/SkillsQuestion'
import TalentPickerQuestion from './TalentPickerQuestion/TalentPickerQuestion'
import TalentPickerQuestionV2 from './TalentPickerQuestion/TalentPickerQuestionV2'
+import JobsPickerQuestion from './JobsPickerQuestion/JobsPickerQuestion'
import SpecFeatureQuestion from './SpecFeatureQuestion'
import ColorSelector from './../../../components/ColorSelector/ColorSelector'
import SelectDropdown from './../../../components/SelectDropdown/SelectDropdown'
@@ -389,6 +390,9 @@ class SpecQuestions extends React.Component {
options: q.options,
})
break
+ case 'jobs-picker':
+ ChildElem = JobsPickerQuestion
+ break
default:
ChildElem = () => (
@@ -459,7 +463,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', 'talent-picker-v2'], 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', 'jobs-picker'], q.type) && q.visibilityForRendering === STEP_VISIBILITY.READ_OPTIMIZED ? (
{
+ const url = `${TAAS_APP_URL}/myteams/${projectId}`
+ return (
+
+ )
+}
+
+export default TaasProjectWelcome
diff --git a/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.scss b/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.scss
new file mode 100644
index 000000000..e0ed62c21
--- /dev/null
+++ b/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.scss
@@ -0,0 +1,15 @@
+@import '~tc-ui/src/styles/tc-includes';
+@import '../../../../styles/includes';
+
+.container {
+ align-items: center;
+ display: flex;
+ justify-content: center;
+ height: 400px;
+
+ :global(.tc-btn-lg) {
+ height: 70px;
+ line-height: 68px;
+ font-size: 30px;
+ }
+}
diff --git a/src/projects/detail/components/TaasProjectWelcome/index.js b/src/projects/detail/components/TaasProjectWelcome/index.js
new file mode 100644
index 000000000..96117cea1
--- /dev/null
+++ b/src/projects/detail/components/TaasProjectWelcome/index.js
@@ -0,0 +1,2 @@
+import TaasProjectWelcome from './TaasProjectWelcome'
+export default TaasProjectWelcome
diff --git a/src/projects/detail/components/TalentPickerQuestion/TalentPickerQuestionV2.jsx b/src/projects/detail/components/TalentPickerQuestion/TalentPickerQuestionV2.jsx
index 5a65a60c9..a37755209 100644
--- a/src/projects/detail/components/TalentPickerQuestion/TalentPickerQuestionV2.jsx
+++ b/src/projects/detail/components/TalentPickerQuestion/TalentPickerQuestionV2.jsx
@@ -70,6 +70,7 @@ class TalentPickerQuestionV2 extends Component {
getDefaultValue() {
const { options } = this.props
return options.map((o) => ({
+ roleTitle: o.roleTitle,
role: o.role,
people: '0',
duration: '0',
@@ -96,13 +97,16 @@ class TalentPickerQuestionV2 extends Component {
}
insertRole(index, role) {
- const { getValue } = this.props
+ const { getValue, options } = this.props
let values = getValue() || this.getDefaultValue()
+ const roleOption = _.find(options, { role })
+
values = [
...values.slice(0, index),
{
role,
+ roleTitle: roleOption.roleTitle,
people: '0',
duration: '0',
skills: [],
diff --git a/src/projects/detail/containers/DashboardContainer.jsx b/src/projects/detail/containers/DashboardContainer.jsx
index 4555b96f7..43420918e 100644
--- a/src/projects/detail/containers/DashboardContainer.jsx
+++ b/src/projects/detail/containers/DashboardContainer.jsx
@@ -40,6 +40,7 @@ import SystemFeed from '../../../components/Feed/SystemFeed'
import ProjectScopeDrawer from '../components/ProjectScopeDrawer'
import ProjectStages from '../components/ProjectStages'
import ProjectPlanEmpty from '../components/ProjectPlanEmpty'
+import TaasProjectWelcome from '../components/TaasProjectWelcome'
import NotificationsReader from '../../../components/NotificationsReader'
import { hasPermission } from '../../../helpers/permissions'
import { getProjectTemplateById } from '../../../helpers/templates'
@@ -60,6 +61,7 @@ import {
PHASE_STATUS_DRAFT,
SCREEN_BREAKPOINT_MD,
CODER_BOT_USERID,
+ PROJECT_TYPE_TALENT_AS_A_SERVICE,
PHASE_PRODUCT_TEMPLATE_ID
} from '../../../config/constants'
@@ -179,6 +181,8 @@ class DashboardContainer extends React.Component {
const isProjectLive = project.status !== PROJECT_STATUS_COMPLETED && project.status !== PROJECT_STATUS_CANCELLED
+ const isTaasProject = project.type === PROJECT_TYPE_TALENT_AS_A_SERVICE
+
const leftArea = (
- {unreadProjectUpdate.length > 0 &&
-
- }
- {/* Toggle drawer */}
- {!!estimationQuestion &&
-
- }
- {/* The following containerStyle and overlayStyle are needed for shrink drawer and overlay size for not
- covering sidebar and topbar
- */}
- this.setState({open})}
- project={project}
- template={template}
- updateProject={updateProject}
- processing={isProcessing}
- fireProjectDirty={fireProjectDirty}
- fireProjectDirtyUndo= {fireProjectDirtyUndo}
- addProjectAttachment={addProjectAttachment}
- updateProjectAttachment={updateProjectAttachment}
- removeProjectAttachment={removeProjectAttachment}
- productTemplates={productTemplates}
- productCategories={productCategories}
- />
-
- {visiblePhases && visiblePhases.length > 0 ? (
-
+ {isTaasProject ? (
+
) : (
-
- )}
- {isCreatingPhase? : null}
- {isProjectLive && !isCreatingPhase && hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) && !isLoadingPhases && (
-
+
+ {unreadProjectUpdate.length > 0 &&
+
+ }
+ {/*
Toggle drawer */}
+ {!!estimationQuestion &&
+
+ }
+ {/* The following containerStyle and overlayStyle are needed for shrink drawer and overlay size for not
+ covering sidebar and topbar
+ */}
+
this.setState({open})}
+ project={project}
+ template={template}
+ updateProject={updateProject}
+ processing={isProcessing}
+ fireProjectDirty={fireProjectDirty}
+ fireProjectDirtyUndo= {fireProjectDirtyUndo}
+ addProjectAttachment={addProjectAttachment}
+ updateProjectAttachment={updateProjectAttachment}
+ removeProjectAttachment={removeProjectAttachment}
+ productTemplates={productTemplates}
+ productCategories={productCategories}
+ />
+
+ {visiblePhases && visiblePhases.length > 0 ? (
+
+ ) : (
+
+ )}
+ {isCreatingPhase? : null}
+ {isProjectLive && !isCreatingPhase && hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) && !isLoadingPhases && (
+
+ )}
+
)}