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 ? (
-
+ ) } - {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 }