diff --git a/.circleci/config.yml b/.circleci/config.yml
index 5b9dace6e..9711ba7ed 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -128,7 +128,7 @@ workflows:
- build-dev
filters:
branches:
- only: ['dev', 'feature/add_msg_for_taas_projects']
+ only: ['dev', 'feature/project-plan-simplification-0']
- deployTest01:
context : org-global
diff --git a/package-lock.json b/package-lock.json
index cfcd2b887..c5b72982d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -140,6 +140,28 @@
"resolved": "https://registry.npmjs.org/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz",
"integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw=="
},
+ "@toast-ui/editor": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@toast-ui/editor/-/editor-2.5.1.tgz",
+ "integrity": "sha512-LVNo/YaNItUemEaRFvFAVn7w/0U7yxEheMdn6GEGxqo727rRZD1MH7OTDVq6NeQ+P93VwFpa0i9GGRBhNNEbPQ==",
+ "requires": {
+ "@types/codemirror": "0.0.71",
+ "codemirror": "^5.48.4"
+ }
+ },
+ "@types/codemirror": {
+ "version": "0.0.71",
+ "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.71.tgz",
+ "integrity": "sha512-b2oEEnno1LIGKMR7uBEsr40al1UijF1HEpRn0+Yf1xOLl24iQgB7DBpZVMM7y54G5wCNoclDrRO65E6KHPNO2w==",
+ "requires": {
+ "@types/tern": "*"
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.47",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz",
+ "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg=="
+ },
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -151,6 +173,14 @@
"integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==",
"dev": true
},
+ "@types/tern": {
+ "version": "0.23.3",
+ "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.3.tgz",
+ "integrity": "sha512-imDtS4TAoTcXk0g7u4kkWqedB3E4qpjXzCpD2LU5M5NAXHzCDsypyvXSaG7mM8DKYkCRa7tFp4tS/lp/Wo7Q3w==",
+ "requires": {
+ "@types/estree": "*"
+ }
+ },
"Base64": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/Base64/-/Base64-0.1.4.tgz",
@@ -3084,6 +3114,11 @@
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
+ "codemirror": {
+ "version": "5.60.0",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.60.0.tgz",
+ "integrity": "sha512-AEL7LhFOlxPlCL8IdTcJDblJm8yrAGib7I+DErJPdZd4l6imx8IMgKK3RblVgBQqz3TZJR4oknQ03bz+uNjBYA=="
+ },
"coffee-loader": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/coffee-loader/-/coffee-loader-0.8.0.tgz",
diff --git a/src/config/constants.js b/src/config/constants.js
index c44a42c72..a1a0179c9 100644
--- a/src/config/constants.js
+++ b/src/config/constants.js
@@ -96,6 +96,12 @@ export const CREATE_TIMELINE_MILESTONE_SUCCESS = 'CREATE_TIMELINE_MILESTONE_SUCC
export const CREATE_TIMELINE_MILESTONE_FAILURE = 'CREATE_TIMELINE_MILESTONE_FAILURE'
export const CREATE_TIMELINE_MILESTONE_PENDING = 'CREATE_TIMELINE_MILESTONE_PENDING'
+// project phases
+export const CREATE_PROJECT_PHASE = 'CREATE_PROJECT_PHASE'
+export const CREATE_PROJECT_PHASE_FAILURE = 'CREATE_PROJECT_PHASE_FAILURE'
+export const CREATE_PROJECT_PHASE_SUCCESS = 'CREATE_PROJECT_PHASE_SUCCESS'
+export const CREATE_PROJECT_PHASE_PENDING = 'CREATE_PROJECT_PHASE_PENDING'
+
// project phases and timeline and milestones
export const CREATE_PROJECT_PHASE_TIMELINE_MILESTONES = 'CREATE_PROJECT_PHASE_TIMELINE_MILESTONES'
export const CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_FAILURE = 'CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_FAILURE'
@@ -613,7 +619,7 @@ export const PHASE_STATUS = [
{color: 'gray', name: 'Draft', fullName: 'Phase is in draft', value: PHASE_STATUS_DRAFT, order: 2, dropDownOrder: 1 },
// {color: 'gray', name: 'In review', fullName: 'Phase is in review', value: PHASE_STATUS_IN_REVIEW, order: 3, dropDownOrder: 2 },
{color: 'gray', name: 'Planned', fullName: 'Phase is reviewed', value: PHASE_STATUS_REVIEWED, order: 4, dropDownOrder: 3 },
- {color: 'green', name: 'Active', fullName: 'Phase is active', value: PHASE_STATUS_ACTIVE, order: 1, dropDownOrder: 4 },
+ {color: 'green', name: 'Published', fullName: 'Phase is active', value: PHASE_STATUS_ACTIVE, order: 1, dropDownOrder: 4 },
{color: 'black', name: 'Completed', fullName: 'Phase is completed', value: PHASE_STATUS_COMPLETED, order: 5, dropDownOrder: 5 },
// {color: 'black', name: 'Cancelled', fullName: 'Phase is canceled', value: PHASE_STATUS_CANCELLED, order: 6, dropDownOrder: 6 },
// {color: 'red', name: 'Paused', fullName: 'Phase is paused', value: PHASE_STATUS_PAUSED, order: 7, dropDownOrder: 7 }
diff --git a/src/config/permissions.js b/src/config/permissions.js
index 4e60f67bd..82e33250d 100644
--- a/src/config/permissions.js
+++ b/src/config/permissions.js
@@ -199,6 +199,20 @@ export const PERMISSIONS = {
],
},
+ SHOW_PHASE_STATUS: {
+ meta: {
+ group: 'Project Plan',
+ title: 'Show project phase status',
+ },
+ projectRoles: [
+ ..._.difference(PROJECT_ALL, [PROJECT_ROLE_CUSTOMER])
+ ],
+ topcoderRoles: [
+ ROLE_CONNECT_MANAGER,
+ ...TOPCODER_ADMINS,
+ ],
+ },
+
/*
Project Members
*/
diff --git a/src/helpers/projectHelper.js b/src/helpers/projectHelper.js
index f27e72f77..bab78e28d 100644
--- a/src/helpers/projectHelper.js
+++ b/src/helpers/projectHelper.js
@@ -158,14 +158,20 @@ export function getPhaseActualData(phase, timeline) {
duration = phase.duration ? phase.duration : 0
progress = phase.progress ? phase.progress : 0
- if (startDate) {
+ // if start date and duration are set, use them to calculate endDate
+ if (!endDate && startDate && duration > 0) {
endDate = startDate.clone().add(duration, 'days')
- } else {
+ }
+ // default to today if start date not set
+ if (!startDate) {
startDate = moment().hours(0).minutes(0).seconds(0).milliseconds(0)
}
+ // default to today if end date not set
if (!endDate) {
endDate = moment().hours(0).minutes(0).seconds(0).milliseconds(0)
}
+ // re-caclulate the duration of the phase
+ duration = endDate.diff(startDate, 'days') + 1
// if phase's product has timeline get data from timeline
} else {
@@ -264,7 +270,7 @@ export function getProjectNavLinks(project, projectId, renderFAQs) {
messagesTab = { label: 'Messages', to: `/projects/${projectId}/messages`, Icon: MessagesIcon, iconClassName: 'stroke', exact: false }
}
// choose set of menu links based on the project version
- const navLinks = project.version === 'v3' ? [
+ const navLinks = ['v3', 'v4'].includes(project.version) ? [
{ label: 'Dashboard', to: `/projects/${projectId}`, Icon: DashboardIcon, iconClassName: 'stroke' },
messagesTab,
{ label: 'Scope', to: `/projects/${projectId}/scope`, Icon: ScopeIcon, iconClassName: 'fill' },
diff --git a/src/projects/actions/project.js b/src/projects/actions/project.js
index 1b2c7d821..a4b64b3ad 100644
--- a/src/projects/actions/project.js
+++ b/src/projects/actions/project.js
@@ -65,7 +65,8 @@ import {
LOAD_PROJECT_MEMBER_INVITES,
CREATE_PROJECT_PHASE_TIMELINE_MILESTONES,
LOAD_PROJECT_MEMBER,
- ES_REINDEX_DELAY
+ ES_REINDEX_DELAY,
+ CREATE_PROJECT_PHASE
} from '../../config/constants'
import {
updateProductMilestone,
@@ -178,6 +179,7 @@ function getProjectPhasesWithProducts(projectId) {
'endDate',
'id',
'name',
+ 'description',
'progress',
'projectId',
'spentBudget',
@@ -273,16 +275,20 @@ function createProductsTimelineAndMilestone(project) {
/**
* Create phase and product for the project
*
- * @param {Object} project project
- * @param {Object} productTemplate product template
- * @param {String} status (optional) project/phase status
+ * @param {Object} project project
+ * @param {Object} productTemplate product template
+ * @param {String} status (optional) project/phase status
+ * @param {Date} startDate start date of the phase
+ * @param {Date} endDate end date of the phase
+ * @param {Boolean} createTimeline flag to indicate if we need to create timeline for the phase
*
* @return {Promise} project
*/
-export function createProjectPhaseAndProduct(project, productTemplate, status = PHASE_STATUS_DRAFT, startDate, endDate) {
+export function createProjectPhaseAndProduct(project, productTemplate, status = PHASE_STATUS_DRAFT, startDate, endDate, createTimeline = true) {
const param = {
status,
name: productTemplate.name,
+ description: productTemplate.description,
productTemplateId: productTemplate.id
}
if (startDate) {
@@ -293,13 +299,21 @@ export function createProjectPhaseAndProduct(project, productTemplate, status =
}
return createProjectPhase(project.id, param).then((phase) => {
- // we also wait until timeline is created as we will load it for the phase after creation
- return createTimelineAndMilestoneForProduct(phase.products[0], phase).then((timeline) => ({
- project,
- phase,
- product:phase.products[0],
- timeline,
- }))
+ if (createTimeline) {
+ // we also wait until timeline is created as we will load it for the phase after creation
+ return createTimelineAndMilestoneForProduct(phase.products[0], phase).then((timeline) => ({
+ project,
+ phase,
+ product:phase.products[0],
+ timeline,
+ }))
+ } else {
+ return Promise.resolve({
+ project,
+ phase,
+ product:phase.products[0]
+ })
+ }
})
}
@@ -331,6 +345,25 @@ function createPhaseAndMilestonesRequest(project, productTemplate, status = PHAS
})
}
+/**
+ * Creates phase and product only, without timeline and milestone. Introduced with project plan simplification
+ * work where we removed timeline and milestones for projects with version v4
+ * @param {*} project
+ * @param {*} productTemplate
+ * @param {*} status
+ * @param {*} startDate
+ * @param {*} endDate
+ */
+export function createPhaseWithoutTimeline(project, productTemplate, status, startDate, endDate) {
+ return (dispatch) => {
+ console.log(CREATE_PROJECT_PHASE)
+ return dispatch({
+ type: CREATE_PROJECT_PHASE,
+ payload: createProjectPhaseAndProduct(project, productTemplate, status, startDate, endDate, false)
+ })
+ }
+}
+
export function createPhaseAndMilestones(project, productTemplate, status, startDate, endDate, milestones) {
return (dispatch, getState) => {
return dispatch({
@@ -340,9 +373,6 @@ export function createPhaseAndMilestones(project, productTemplate, status, start
const state = getState()
const project = state.projectState.project
- console.log('project.status', project.status)
- console.log('status', status)
-
// if phase is created as ACTIVE, move project to ACTIVE too
if (
_.includes([PROJECT_STATUS_DRAFT, PROJECT_STATUS_IN_REVIEW, PROJECT_STATUS_REVIEWED], project.status) &&
@@ -464,6 +494,7 @@ export function updatePhase(projectId, phaseId, updatedProps, phaseIndex) {
const phaseStartDate = timeline ? timeline.startDate : phase.startDate
const startDateChanged = updatedProps.startDate ? updatedProps.startDate.diff(phaseStartDate) : null
const phaseActivated = phaseStatusChanged && updatedProps.status === PHASE_STATUS_ACTIVE
+ const projectVersion = state.projectState.project.version
if (updatedProps.startDate) {
updatedProps.startDate = moment(updatedProps.startDate).format('YYYY-MM-DD')
@@ -494,7 +525,7 @@ export function updatePhase(projectId, phaseId, updatedProps, phaseIndex) {
// - phase's status is changed to active
// - there is not active milestone alreay (this can happen when phase is made active more than once
// e.g. Active => Paused => Active)
- if (timeline && !activeMilestone && phaseActivated ) {
+ if (projectVersion !== 'v4' && timeline && !activeMilestone && phaseActivated ) {
dispatch(
updateProductMilestone(
productId,
diff --git a/src/projects/actions/projectDashboard.js b/src/projects/actions/projectDashboard.js
index b7d9db0de..efb9127e2 100644
--- a/src/projects/actions/projectDashboard.js
+++ b/src/projects/actions/projectDashboard.js
@@ -33,7 +33,7 @@ const getDashboardData = (dispatch, getState, projectId, isOnlyLoadProjectInfo)
}
// for new projects load phases, products, project template and product templates
- if (project.version === 'v3') {
+ if (['v3', 'v4'].indexOf(project.version) !== -1) {
promises.push(
dispatch(loadProjectPlan(projectId, userIds))
)
diff --git a/src/projects/components/projectsCard/ProjectCard.jsx b/src/projects/components/projectsCard/ProjectCard.jsx
index 1265508ec..eba8078c8 100644
--- a/src/projects/components/projectsCard/ProjectCard.jsx
+++ b/src/projects/components/projectsCard/ProjectCard.jsx
@@ -15,7 +15,7 @@ function ProjectCard({ project, disabled, currentUser, history, onChangeStatus,
const isMember = _.some(project.members, m => (m.userId === currentUser.userId && m.deletedAt === null))
// check whether has pending invition
const isInvited = _.some(project.invites, m => ((m.userId === currentUser.userId || m.email === currentUser.email) && !m.deletedAt && m.status === 'pending'))
- const projectDetailsURL = project.version === 'v3'
+ const projectDetailsURL = ['v3', 'v4'].includes(project.version)
? `/projects/${project.id}/scope`
: `/projects/${project.id}/specification`
diff --git a/src/projects/components/projectsCard/ProjectCardBody.jsx b/src/projects/components/projectsCard/ProjectCardBody.jsx
index 12b124625..3e5bcbb5b 100644
--- a/src/projects/components/projectsCard/ProjectCardBody.jsx
+++ b/src/projects/components/projectsCard/ProjectCardBody.jsx
@@ -32,7 +32,7 @@ function ProjectCardBody({ project, descLinesCount = 8,
const progress = _.get(process, 'percent', 0)
- const projectDetailsURL = project.version === 'v3'
+ const projectDetailsURL = ['v3', 'v4'].includes(project.version)
? `/projects/${project.id}/scope`
: `/projects/${project.id}/specification`
diff --git a/src/projects/create/containers/CreateContainer.jsx b/src/projects/create/containers/CreateContainer.jsx
index 5e687c758..3f2612f83 100644
--- a/src/projects/create/containers/CreateContainer.jsx
+++ b/src/projects/create/containers/CreateContainer.jsx
@@ -245,8 +245,10 @@ class CreateContainer extends React.Component {
}
_.set(project, 'details.utm.google', googleAnalytics)
}
+ const searchParams = new URLSearchParams(window.location.search)
+ const isBetaMode = searchParams.get('beta') === 'true'
if (projectTemplate) {
- project.version = 'v3'
+ project.version = isBetaMode ? 'v4' : 'v3'
project.templateId = projectTemplate.id
project.type = projectTemplate.category
}
diff --git a/src/projects/detail/ProjectDetail.jsx b/src/projects/detail/ProjectDetail.jsx
index f44ae71f7..f479e37da 100644
--- a/src/projects/detail/ProjectDetail.jsx
+++ b/src/projects/detail/ProjectDetail.jsx
@@ -169,7 +169,7 @@ class ProjectDetail extends Component {
// if project version not v3 , URL /scope redirect to /specification
if(project
&& project.version
- && project.version !== 'v3'
+ && !['v3', 'v4'].includes(project.version)
&& project.id === parseInt(match.params.projectId)
&& this.props.history.location.pathname.indexOf('/scope') !== -1 ){
this.props.history.push(this.props.history.location.pathname.replace('/scope', '/specification'))
diff --git a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx
index 352e1d994..a9547b40e 100644
--- a/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx
+++ b/src/projects/detail/components/CreatePhaseForm/CreatePhaseForm.jsx
@@ -27,7 +27,7 @@ const getMilestoneModelByIndex = (model, index) => {
let milestoneModel
// omit phase fields
- _.forEach(_.keys(_.omit(model, ['title', 'startDate', 'endDate'])), (key) => {
+ _.forEach(_.keys(_.omit(model, ['title', 'description', 'startDate', 'endDate'])), (key) => {
const keyMatches = key.match(/(\w+)_(\d+)/)
if (keyMatches.length !== 3) {
@@ -120,6 +120,7 @@ class CreatePhaseForm extends React.Component {
const phaseData = {
title: model.title,
+ description: model.description || ' ',
startDate: moment(model.startDate),
endDate: moment(model.endDate),
}
@@ -127,6 +128,9 @@ class CreatePhaseForm extends React.Component {
const apiMilestones = milestones.map((milestone, index) => ({
// default values
...MILESTONE_DEFAULT_VALUES[milestone.type],
+ ..._.omit(milestone, 'pseudoId'),
+ startDate: model.startDate,
+ endDate: model.endDate,
// values from the form
...getMilestoneModelByIndex(model, index),
@@ -152,12 +156,14 @@ class CreatePhaseForm extends React.Component {
* @param change changed form model in flattened form
*/
handleChange(change) {
- const {
- milestones
- } = this.state
+ const { projectVersion } = this.props
+ // DO NOT update milestones state if project version is 4
+ if (projectVersion === 4) return
+ const { milestones } = this.state
// update all milestones in state from the Formzy model
const newMilestones = milestones.map((milestone, index) => {
+
const milestoneModel = getMilestoneModelByIndex(change, index)
const updatedMilestone = {
@@ -216,7 +222,7 @@ class CreatePhaseForm extends React.Component {
const {
milestones
} = this.state
-
+
const ms = _.map(milestones, (m, index) => {
return (
@@ -322,6 +328,10 @@ class CreatePhaseForm extends React.Component {
render() {
const { isAddButtonClicked } = this.state
+ const { projectVersion } = this.props
+
+ // const searchParams = new URLSearchParams(window.location.search)
+ // const isBetaMode = searchParams.get('beta') === 'true'
if (!isAddButtonClicked) {
return (
@@ -357,6 +367,17 @@ class CreatePhaseForm extends React.Component {
maxLength={48}
/>
+
+
+
+
- {this.renderTab()}
- {this.renderMilestones()}
+ { projectVersion !== 4 && this.renderTab()}
+ { projectVersion !== 4 && this.renderMilestones()}
+ {phase.description && (
+
+
+
+
+ )}
{
+const getVisualPhaseStatus = (attr, projectVersion) => {
// if model doesn't have status, fallback for DRAFT
let status = attr.status ? attr.status : PHASE_STATUS_DRAFT
@@ -66,6 +66,12 @@ const getVisualPhaseStatus = (attr) => {
}
}
+ if (projectVersion === 'v4') {
+ if (status === PHASE_STATUS_ACTIVE) {
+ visualStatus = PHASE_STATUS_ACTIVE
+ }
+ }
+
return visualStatus
}
@@ -127,10 +133,11 @@ class PhaseCard extends React.Component {
hasUnseen,
phaseId,
isExpanded,
+ projectVersion,
} = this.props
const progressInPercent = attr.progressInPercent || 0
- const status = getVisualPhaseStatus(attr)
+ const status = getVisualPhaseStatus(attr, projectVersion)
const statusDetails = _.find(PHASE_STATUS, s => s.value === status)
const phaseEditable =
@@ -139,6 +146,8 @@ class PhaseCard extends React.Component {
hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN)
&& ( status !== PHASE_STATUS_COMPLETED || hasPermission(PERMISSIONS.MANAGE_COMPLETED_PHASE) )
)
+ // const searchParams = new URLSearchParams(window.location.search)
+ const isSimplePlan = projectVersion === 'v4'
return (
@@ -155,7 +164,7 @@ class PhaseCard extends React.Component {
{(matches) => (matches || !isExpanded ? (
-
+
@@ -173,10 +182,12 @@ class PhaseCard extends React.Component {
{phaseEditable && !this.state.isEditting && (
)}
+ {attr.phase.description && attr.phase.description.trim().length > 0 &&
{attr.phase.description}
}
- {attr.duration}
- {attr.startEndDates}
- {attr.posts && {attr.posts}}
+ {attr.duration}
+ {attr.actualStartDate.format('YYYY-MM-DD')}
+ {attr.actualEndDate.format('YYYY-MM-DD')}
+ {!isSimplePlan && attr.posts && {attr.posts}}
@@ -201,7 +212,7 @@ class PhaseCard extends React.Component {
)
}
- {status && status === PHASE_STATUS_ACTIVE &&
+ { status && status === PHASE_STATUS_ACTIVE &&
(
{attr.price}
@@ -214,7 +225,7 @@ class PhaseCard extends React.Component {
}
- {status && status !== PHASE_STATUS_ACTIVE &&
+ {status && status !== PHASE_STATUS_ACTIVE && (!isSimplePlan || hasPermission(PERMISSIONS.SHOW_PHASE_STATUS)) &&
(
{statusDetails.name}
@@ -222,24 +233,26 @@ class PhaseCard extends React.Component {
)
}
- {status && status === PHASE_STATUS_ACTIVE &&
+ { status && status === PHASE_STATUS_ACTIVE && (!isSimplePlan || hasPermission(PERMISSIONS.SHOW_PHASE_STATUS)) &&
(
-
- {progressInPercent}% completed
-
+ { !isSimplePlan && (
+
+ {progressInPercent}% completed
+
+ )}
{statusDetails.name}
)
}
- {!this.state.isEditting && (
+ { !isSimplePlan && !this.state.isEditting && (
)}
{status && status === PHASE_STATUS_ACTIVE &&
diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.scss b/src/projects/detail/components/PhaseCard/PhaseCard.scss
index 7570a0f5a..5c02b17e8 100644
--- a/src/projects/detail/components/PhaseCard/PhaseCard.scss
+++ b/src/projects/detail/components/PhaseCard/PhaseCard.scss
@@ -67,6 +67,9 @@
width: 8px;
}
}
+ .simple-plan {
+ cursor: default;
+ }
.col {
&:nth-child(1) {
flex: 1 500px;
@@ -157,15 +160,19 @@
.meta {
line-height: 20px;
position: relative;
- &:after {
- content: " • ";
+ margin-left: 10px;
+
+ label {
display: inline-block;
- margin: 0 4px;
+ color: $tc-gray-50;
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 20px;
+ margin-right: 5px;
}
- &:last-child {
- &:after {
- display: none;
- }
+
+ &:first-child {
+ margin-left: 0px;
}
}
}
@@ -212,6 +219,12 @@
justify-content: space-between;
}
}
+ .project-description{
+ margin-bottom: 5px;
+ @include roboto;
+ color: $tc-black;
+ font-size: 13px;
+ }
.edit-btn {
width: 40px;
height: 40px;
diff --git a/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.jsx b/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.jsx
index 1b525983f..731bc863c 100644
--- a/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.jsx
+++ b/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.jsx
@@ -9,15 +9,17 @@ import './PhaseCardListFooter.scss'
const PhaseCardListFooter = ({
duration,
price,
- startEndDates
+ minStartDate,
+ maxEndDate,
}) => (
Total:
- {duration}
- {startEndDates}
+ {duration}
+ {minStartDate.format('YYYY-MM-DD')}
+ {maxEndDate.format('YYYY-MM-DD')}
{parseInt(price, 10) > 0 &&
price
}
@@ -29,14 +31,16 @@ const PhaseCardListFooter = ({
PhaseCardListFooter.defaultProps = {
duration: null,
price: null,
- startEndDates: null,
+ minStartDate: null,
+ maxEndDate: null,
projectId: 0
}
PhaseCardListFooter.propTypes = {
duration: PT.string,
price: PT.string,
- startEndDates: PT.string
+ minStartDate: PT.Date,
+ maxEndDate: PT.Date,
}
export default PhaseCardListFooter
diff --git a/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.scss b/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.scss
index e75d0e0a2..215ecb783 100644
--- a/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.scss
+++ b/src/projects/detail/components/PhaseCardListFooter/PhaseCardListFooter.scss
@@ -51,14 +51,17 @@
.meta-list {
.meta {
- &::after {
- content: " • ";
+ color: $tc-gray-30;
+ margin-left: 10px;
+
+ label {
display: inline-block;
- margin: 0 $base-unit;
+ color: $tc-white;
+ margin-right: 5px;
}
- &:last-child::after {
- display: none;
+ &:first-child {
+ margin-left: 0px;
}
&.meta-dark {
diff --git a/src/projects/detail/components/PhaseCardListHeader/PhaseCardListHeader.scss b/src/projects/detail/components/PhaseCardListHeader/PhaseCardListHeader.scss
index 58d4977c2..e2f1bddc7 100644
--- a/src/projects/detail/components/PhaseCardListHeader/PhaseCardListHeader.scss
+++ b/src/projects/detail/components/PhaseCardListHeader/PhaseCardListHeader.scss
@@ -25,6 +25,7 @@
}
.status {
+ display: none;
flex: 0 55px;
min-width: 55px;
text-align: center;
diff --git a/src/projects/detail/components/ProjectStage.jsx b/src/projects/detail/components/ProjectStage.jsx
index 751841f67..24be9434a 100644
--- a/src/projects/detail/components/ProjectStage.jsx
+++ b/src/projects/detail/components/ProjectStage.jsx
@@ -57,13 +57,13 @@ function formatPhaseCardAttr(phase, phaseIndex, productTemplates, feed, timeline
} = getPhaseActualData(phase, timeline)
const duration = `${plannedDuration} day${plannedDuration !== 1 ? 's' : ''}`
- let startEndDates = startDate ? `${startDate.format('MMM D')}` : ''
- // appends end date to the start date only if end date is greater than start date
- startEndDates += startDate && endDate && endDate.diff(startDate, 'days') > 0 ? `-${endDate.format('MMM D')}` : ''
- // extracts the start date's month string plus white space
- const monthStr = startEndDates.substr(0, 4)
- // replaces the second occurrence of the month part i.e. removes the end date's month part
- startEndDates = startEndDates.lastIndexOf(monthStr) !== 0 ? startEndDates.replace(`-${monthStr}`, '-') : startEndDates
+ // let startEndDates = startDate ? `${startDate.format('MMM D')}` : ''
+ // // appends end date to the start date only if end date is greater than start date
+ // startEndDates += startDate && endDate && endDate.diff(startDate, 'days') > 0 ? `-${endDate.format('MMM D')}` : ''
+ // // extracts the start date's month string plus white space
+ // const monthStr = startEndDates.substr(0, 4)
+ // // replaces the second occurrence of the month part i.e. removes the end date's month part
+ // startEndDates = startEndDates.lastIndexOf(monthStr) !== 0 ? startEndDates.replace(`-${monthStr}`, '-') : startEndDates
const actualPrice = phase.spentBudget
let paidStatus = 'Quoted'
@@ -81,7 +81,7 @@ function formatPhaseCardAttr(phase, phaseIndex, productTemplates, feed, timeline
icon,
title,
duration,
- startEndDates,
+ // startEndDates,
price,
paidStatus,
status,
@@ -216,6 +216,7 @@ class ProjectStage extends React.Component{
deleteProjectPhase(project.id, phase.id)}
timeline={timeline}
hasUnseen={hasAnyNotifications}
@@ -225,50 +226,52 @@ class ProjectStage extends React.Component{
expandProjectPhase={expandProjectPhase}
project={project}
>
-
-
-
- {currentActiveTab === 'timeline' &&
-
- }
-
- {currentActiveTab === 'posts' && (
-
- )}
-
- {currentActiveTab === 'specification' &&
-
-
- updateProduct(project.id, phase.id, product.id, model)}
- saving={isProcessing}
- fireProjectDirty={(values) => fireProductDirty(phase.id, product.id, values)}
- fireProjectDirtyUndo= {fireProductDirtyUndo}
- addAttachment={this.addProductAttachment}
- updateAttachment={this.updateProductAttachment}
- removeAttachment={this.removeProductAttachment}
- attachmentsStorePath={attachmentsStorePath}
- canManageAttachments={hasPermission(PERMISSIONS.EDIT_PROJECT_SPECIFICATION)}
- disableAutoScrolling
- />
-
- }
-
+ { project.version === 'v3' &&
+
+
+
+ {currentActiveTab === 'timeline' &&
+
+ }
+
+ {currentActiveTab === 'posts' && (
+
+ )}
+
+ {currentActiveTab === 'specification' &&
+
+
+ updateProduct(project.id, phase.id, product.id, model)}
+ saving={isProcessing}
+ fireProjectDirty={(values) => fireProductDirty(phase.id, product.id, values)}
+ fireProjectDirtyUndo= {fireProductDirtyUndo}
+ addAttachment={this.addProductAttachment}
+ updateAttachment={this.updateProductAttachment}
+ removeAttachment={this.removeProductAttachment}
+ attachmentsStorePath={attachmentsStorePath}
+ canManageAttachments={hasPermission(PERMISSIONS.EDIT_PROJECT_SPECIFICATION)}
+ disableAutoScrolling
+ />
+
+ }
+
+ }
)
}
diff --git a/src/projects/detail/components/ProjectStages.jsx b/src/projects/detail/components/ProjectStages.jsx
index 7953ea54f..36f624a58 100644
--- a/src/projects/detail/components/ProjectStages.jsx
+++ b/src/projects/detail/components/ProjectStages.jsx
@@ -43,8 +43,8 @@ function formatPhaseCardListFooterProps(phases, productsTimelines) {
const minStartDate = startDates.length > 0 ? moment.min(startDates) : null
const maxEndDate = endDates.length > 0 ? moment.max(endDates) : null
- let startEndDates = minStartDate ? `${minStartDate.format('MMM D')}` : ''
- startEndDates += minStartDate && maxEndDate ? `–${maxEndDate.format('MMM D')}` : ''
+ // let startEndDates = minStartDate ? `${minStartDate.format('MMM D')}` : ''
+ // startEndDates += minStartDate && maxEndDate ? `–${maxEndDate.format('MMM D')}` : ''
const totalPrice = _.sumBy(filteredPhases, 'budget')
@@ -54,7 +54,9 @@ function formatPhaseCardListFooterProps(phases, productsTimelines) {
return {
duration,
- startEndDates,
+ // startEndDates,
+ minStartDate,
+ maxEndDate,
price
}
}
diff --git a/src/projects/detail/containers/DashboardContainer.jsx b/src/projects/detail/containers/DashboardContainer.jsx
index 43420918e..329925369 100644
--- a/src/projects/detail/containers/DashboardContainer.jsx
+++ b/src/projects/detail/containers/DashboardContainer.jsx
@@ -28,7 +28,8 @@ import {
expandProjectPhase,
collapseProjectPhase,
collapseAllProjectPhases,
- createPhaseAndMilestones
+ createPhaseAndMilestones,
+ createPhaseWithoutTimeline,
} from '../../actions/project'
import { addProductAttachment, updateProductAttachment, removeProductAttachment } from '../../actions/projectAttachment'
@@ -118,14 +119,18 @@ class DashboardContainer extends React.Component {
}
onFormSubmit(type, phase, milestones) {
- const { project, createPhaseAndMilestones } = this.props
+ const { project, createPhaseAndMilestones, createPhaseWithoutTimeline } = this.props
const productTemplate = {
name: phase.title,
+ description: phase.description,
id: PHASE_PRODUCT_TEMPLATE_ID,
}
-
- createPhaseAndMilestones(project, productTemplate, type, phase.startDate, phase.endDate, milestones)
+ if (project.version === 'v4') {
+ createPhaseWithoutTimeline(project, productTemplate, type, phase.startDate, phase.endDate)
+ } else {
+ createPhaseAndMilestones(project, productTemplate, type, phase.startDate, phase.endDate, milestones)
+ }
}
@@ -284,6 +289,7 @@ class DashboardContainer extends React.Component {
{isCreatingPhase?
: null}
{isProjectLive && !isCreatingPhase && hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) && !isLoadingPhases && (
)}
@@ -324,6 +330,7 @@ const mapDispatchToProps = {
toggleBundledNotificationRead,
updateProduct,
createPhaseAndMilestones,
+ createPhaseWithoutTimeline,
fireProductDirty,
fireProductDirtyUndo,
addProductAttachment,
diff --git a/src/projects/detail/containers/SecondaryToolBarContainer.jsx b/src/projects/detail/containers/SecondaryToolBarContainer.jsx
index 0ad5e252e..74c9a4009 100644
--- a/src/projects/detail/containers/SecondaryToolBarContainer.jsx
+++ b/src/projects/detail/containers/SecondaryToolBarContainer.jsx
@@ -21,7 +21,7 @@ const SecondaryToolBarContainer = ({
}
// choose set of menu links based on the project version
- const navLinks = project.version === 'v3' ? [
+ const navLinks = ['v3', 'v4'].includes(project.version) ? [
{ label: 'Dashboard', to: `/projects/${match.params.projectId}` },
{ label: 'Messages', to: `/projects/${match.params.projectId}/messages` },
{ label: 'Scope', to: `/projects/${match.params.projectId}/scope` },
diff --git a/src/projects/reducers/project.js b/src/projects/reducers/project.js
index d48a85a8f..5ceb3a85d 100644
--- a/src/projects/reducers/project.js
+++ b/src/projects/reducers/project.js
@@ -24,7 +24,7 @@ import {
ACCEPT_OR_REFUSE_INVITE_SUCCESS, ACCEPT_OR_REFUSE_INVITE_FAILURE, ACCEPT_OR_REFUSE_INVITE_PENDING,
UPLOAD_PROJECT_ATTACHMENT_FILES, DISCARD_PROJECT_ATTACHMENT, CHANGE_ATTACHMENT_PERMISSION,
CREATE_SCOPE_CHANGE_REQUEST_SUCCESS, APPROVE_SCOPE_CHANGE_SUCCESS, REJECT_SCOPE_CHANGE_SUCCESS, CANCEL_SCOPE_CHANGE_SUCCESS, ACTIVATE_SCOPE_CHANGE_SUCCESS,
- LOAD_PROJECT_MEMBERS_SUCCESS, LOAD_PROJECT_MEMBER_INVITES_SUCCESS, LOAD_PROJECT_MEMBER_SUCCESS
+ LOAD_PROJECT_MEMBERS_SUCCESS, LOAD_PROJECT_MEMBER_INVITES_SUCCESS, LOAD_PROJECT_MEMBER_SUCCESS, CREATE_PROJECT_PHASE_PENDING, CREATE_PROJECT_PHASE_SUCCESS
} from '../../config/constants'
import _ from 'lodash'
import update from 'react-addons-update'
@@ -147,10 +147,12 @@ export const projectState = function (state=initialState, action) {
switch (action.type) {
case CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_PENDING:
+ case CREATE_PROJECT_PHASE_PENDING:
return Object.assign({}, state, {
isCreatingPhase: true
})
- case CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS: {
+ case CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS:
+ case CREATE_PROJECT_PHASE_SUCCESS: {
const { phase, product } = action.payload
phase.products = [product]
return update(state, {
diff --git a/src/reducers/alerts.js b/src/reducers/alerts.js
index 647058900..9ed828d00 100644
--- a/src/reducers/alerts.js
+++ b/src/reducers/alerts.js
@@ -68,7 +68,8 @@ import {
APPROVE_SCOPE_CHANGE_FAILURE,
REJECT_SCOPE_CHANGE_FAILURE,
CANCEL_SCOPE_CHANGE_FAILURE,
- ACTIVATE_SCOPE_CHANGE_FAILURE
+ ACTIVATE_SCOPE_CHANGE_FAILURE,
+ CREATE_PROJECT_PHASE_SUCCESS
} from '../config/constants'
/* eslint-enable no-unused-vars */
@@ -83,7 +84,8 @@ export default function(state = {}, action) {
return state
}
- case CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS: {
+ case CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS:
+ case CREATE_PROJECT_PHASE_SUCCESS: {
Alert.success('Project phase created.')
return state
}