diff --git a/src/config/constants.js b/src/config/constants.js
index 9aa0cabc3..bb027afa4 100644
--- a/src/config/constants.js
+++ b/src/config/constants.js
@@ -186,6 +186,10 @@ export const DELETE_PHASE_FEED_COMMENT_PENDING = 'DELETE_PHASE_FEED_COMMENT_PEN
export const DELETE_PHASE_FEED_COMMENT_SUCCESS = 'DELETE_PHASE_FEED_COMMENT_SUCCESS'
export const DELETE_PHASE_FEED_COMMENT_FAILURE = 'DELETE_PHASE_FEED_COMMENT_FAILURE'
+export const EXPAND_PROJECT_PHASE = 'EXPAND_PROJECT_PHASE'
+export const COLLAPSE_PROJECT_PHASE = 'COLLAPSE_PROJECT_PHASE'
+export const COLLAPSE_ALL_PROJECT_PHASES = 'COLLAPSE_ALL_PROJECT_PHASES'
+
// Project Sort
export const PROJECT_SORT = 'PROJECT_SORT'
export const PROJECT_SORT_FAILURE = 'PROJECT_SORT_FAILURE'
diff --git a/src/projects/actions/project.js b/src/projects/actions/project.js
index 99e511969..195049bc2 100644
--- a/src/projects/actions/project.js
+++ b/src/projects/actions/project.js
@@ -48,13 +48,56 @@ import {
PROJECT_STATUS_IN_REVIEW,
PHASE_STATUS_REVIEWED,
PROJECT_STATUS_REVIEWED,
- PROJECT_STATUS_ACTIVE
+ PROJECT_STATUS_ACTIVE,
+ EXPAND_PROJECT_PHASE,
+ COLLAPSE_PROJECT_PHASE,
+ COLLAPSE_ALL_PROJECT_PHASES,
} from '../../config/constants'
import {
updateProductMilestone,
updateProductTimeline
} from './productsTimelines'
+/**
+ * Expand phase and optionaly expand particular tab
+ *
+ * @param {Number} phaseId phase id
+ * @param {String} tab (optional) tab id
+ */
+export function expandProjectPhase(phaseId, tab) {
+ return (dispatch) => {
+ return dispatch({
+ type: EXPAND_PROJECT_PHASE,
+ payload: { phaseId, tab }
+ })
+ }
+}
+
+/**
+ * Collapse phase
+ *
+ * @param {Number} phaseId phase id
+ */
+export function collapseProjectPhase(phaseId) {
+ return (dispatch) => {
+ return dispatch({
+ type: COLLAPSE_PROJECT_PHASE,
+ payload: { phaseId }
+ })
+ }
+}
+
+/**
+ * Collapse all phases and reset tabs to default
+ */
+export function collapseAllProjectPhases() {
+ return (dispatch) => {
+ return dispatch({
+ type: COLLAPSE_ALL_PROJECT_PHASES,
+ })
+ }
+}
+
export function loadProject(projectId) {
return (dispatch) => {
return dispatch({
@@ -446,7 +489,7 @@ export function updatePhase(projectId, phaseId, updatedProps, phaseIndex) {
// if one phase moved to REVIEWED status, make project IN_REVIEW too
if (
- _.includes([PROJECT_STATUS_DRAFT], project.status) &&
+ _.includes([PROJECT_STATUS_DRAFT], project.status) &&
phase.status !== PHASE_STATUS_REVIEWED &&
updatedProps.status === PHASE_STATUS_REVIEWED
) {
@@ -459,7 +502,7 @@ export function updatePhase(projectId, phaseId, updatedProps, phaseIndex) {
// if one phase moved to ACTIVE status, make project ACTIVE too
if (
- _.includes([PROJECT_STATUS_DRAFT, PROJECT_STATUS_IN_REVIEW, PROJECT_STATUS_REVIEWED], project.status) &&
+ _.includes([PROJECT_STATUS_DRAFT, PROJECT_STATUS_IN_REVIEW, PROJECT_STATUS_REVIEWED], project.status) &&
phase.status !== PHASE_STATUS_ACTIVE &&
updatedProps.status === PHASE_STATUS_ACTIVE
) {
diff --git a/src/projects/detail/ProjectDetail.jsx b/src/projects/detail/ProjectDetail.jsx
index fc6bec1e2..6a8edbad6 100644
--- a/src/projects/detail/ProjectDetail.jsx
+++ b/src/projects/detail/ProjectDetail.jsx
@@ -6,6 +6,7 @@ import { connect } from 'react-redux'
import _ from 'lodash'
import { renderComponent, branch, compose, withProps } from 'recompose'
import { loadProjectDashboard } from '../actions/projectDashboard'
+import { clearLoadedProject } from '../actions/project'
import {
LOAD_PROJECT_FAILURE, PROJECT_ROLE_CUSTOMER, PROJECT_ROLE_OWNER,
ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN, ROLE_CONNECT_COPILOT, ROLE_CONNECT_MANAGER
@@ -77,6 +78,10 @@ class ProjectDetail extends Component {
this.props.loadProjectDashboard(projectId)
}
+ componentWillUnmount() {
+ this.props.clearLoadedProject()
+ }
+
componentWillReceiveProps({isProcessing, isLoading, error, project, match}) {
// handle just deleted projects
if (! (error || isLoading || isProcessing) && _.isEmpty(project))
@@ -138,7 +143,7 @@ const mapStateToProps = ({projectState, projectDashboard, loadUser, productsTime
}
}
-const mapDispatchToProps = { loadProjectDashboard }
+const mapDispatchToProps = { loadProjectDashboard, clearLoadedProject }
ProjectDetail.propTypes = {
project: PropTypes.object,
diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx
index 3c3d439f2..7e0bb1df2 100644
--- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx
+++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx
@@ -37,9 +37,7 @@ class PhaseCard extends React.Component {
this.onClose = this.onClose.bind(this)
this.state = {
- isExpanded: '',
isEditting: false,
- isDetailView: false
}
}
@@ -56,11 +54,11 @@ class PhaseCard extends React.Component {
}
toggleCardView() {
- this.setState({
- isDetailView: true,
- isExpanded: !this.state.isExpanded
-
- })
+ if (this.props.isExpanded) {
+ this.props.collapseProjectPhase(this.props.phaseId)
+ } else {
+ this.props.expandProjectPhase(this.props.phaseId)
+ }
}
toggleEditView(e) {
@@ -71,10 +69,7 @@ class PhaseCard extends React.Component {
}
onClose(){
- this.setState({
- isDetailView: false,
- isExpanded: false
- })
+ this.props.collapseProjectPhase(this.props.phaseId)
}
render() {
@@ -86,6 +81,8 @@ class PhaseCard extends React.Component {
isUpdating,
timeline,
hasReadPosts,
+ phaseId,
+ isExpanded,
} = this.props
const progressInPercent = attr.progressInPercent || 0
@@ -99,12 +96,12 @@ class PhaseCard extends React.Component {
const hasUnseen = hasReadPosts
return (
-
+
{
- {(matches) => (matches || !this.state.isDetailView ? (
+ {(matches) => (matches || !isExpanded ? (
-
+
@@ -191,26 +188,28 @@ class PhaseCard extends React.Component {
{!this.state.isEditting && (
{this.props.children}
)}
- {(
- {!isUpdating && (
-
- )}
- {canDelete && !isUpdating && (
- {
- if (confirm(`Are you sure you want to delete phase '${attr.phase.name}'?`)) {
- deleteProjectPhase()
- }
- }}
- />
- )}
- {isUpdating && }
-
)}
+ {isManageUser && this.state.isEditting && (
+
+ {!isUpdating && (
+
+ )}
+ {canDelete && !isUpdating && (
+ {
+ if (confirm(`Are you sure you want to delete phase '${attr.phase.name}'?`)) {
+ deleteProjectPhase()
+ }
+ }}
+ />
+ )}
+ {isUpdating && }
+
+ )}
):(
@@ -248,14 +247,15 @@ PhaseCard.propTypes = {
attr: PT.shape({
duration: PT.string.isRequired,
icon: PT.string.isRequired,
- isExpanded: PT.bool,
paidStatus: PT.string.isRequired,
posts: PT.string,
startEndDates: PT.string.isRequired,
status: PT.string.isRequired,
title: PT.string.isRequired,
hasReadPosts: PT.bool,
- })
+ }),
+ phaseId: PT.number.isRequired,
+ isExpanded: PT.bool,
}
diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.scss b/src/projects/detail/components/PhaseCard/PhaseCard.scss
index 913a171b0..f9e7bfa93 100644
--- a/src/projects/detail/components/PhaseCard/PhaseCard.scss
+++ b/src/projects/detail/components/PhaseCard/PhaseCard.scss
@@ -206,7 +206,11 @@
display: flex;
align-items: center;
position: relative;
- justify-content: space-between;
+ justify-content: flex-start;
+
+ @media screen and (max-width: $screen-md - 1px) {
+ justify-content: space-between;
+ }
}
.edit-btn {
width: 40px;
@@ -215,7 +219,11 @@
background-size: 15px;
cursor: pointer;
transition: 0.25s;
- margin: auto auto auto 0;
+ flex: 0 0 40px;
+
+ @media screen and (max-width: $screen-md - 1px) {
+ margin-right: -30px;
+ }
}
.toggle-arrow {
position: absolute;
diff --git a/src/projects/detail/components/ProjectStage.jsx b/src/projects/detail/components/ProjectStage.jsx
index c1a793c42..946aadf52 100644
--- a/src/projects/detail/components/ProjectStage.jsx
+++ b/src/projects/detail/components/ProjectStage.jsx
@@ -4,7 +4,6 @@
import React from 'react'
import PT from 'prop-types'
import _ from 'lodash'
-import uncontrollable from 'uncontrollable'
import { formatNumberWithCommas } from '../../../helpers/format'
import { getPhaseActualData } from '../../../helpers/projectHelper'
@@ -93,6 +92,7 @@ class ProjectStage extends React.Component{
this.removeProductAttachment = this.removeProductAttachment.bind(this)
this.updateProductAttachment = this.updateProductAttachment.bind(this)
this.addProductAttachment = this.addProductAttachment.bind(this)
+ this.onTabClick = this.onTabClick.bind(this)
}
removeProductAttachment(attachmentId) {
@@ -116,9 +116,14 @@ class ProjectStage extends React.Component{
addProductAttachment(project.id, phase.id, product.id, attachment)
}
+ onTabClick(tab) {
+ const { expandProjectPhase, phase } = this.props
+
+ expandProjectPhase(phase.id, tab)
+ }
+
render() {
const {
- activeTab,
phase,
phaseIndex,
project,
@@ -130,8 +135,10 @@ class ProjectStage extends React.Component{
updateProduct,
fireProductDirty,
fireProductDirtyUndo,
- onTabClick,
deleteProjectPhase,
+ phaseState,
+ collapseProjectPhase,
+ expandProjectPhase,
// comes from phaseFeedHOC
currentUser,
@@ -156,7 +163,7 @@ class ProjectStage extends React.Component{
const hasTimeline = !!timeline
const defaultActiveTab = hasTimeline ? 'timeline' : 'posts'
- const currentActiveTab = activeTab ? activeTab : defaultActiveTab
+ const currentActiveTab = _.get(phaseState, 'tab', defaultActiveTab)
const postNotifications = filterNotificationsByPosts(notifications, _.get(feed, 'posts', []))
const unreadPostNotifications = filterReadNotifications(postNotifications)
const hasReadPosts = unreadPostNotifications.length > 0
@@ -169,11 +176,15 @@ class ProjectStage extends React.Component{
deleteProjectPhase={() => deleteProjectPhase(project.id, phase.id)}
timeline={timeline}
hasReadPosts={hasReadPosts}
+ phaseId={phase.id}
+ isExpanded={_.get(phaseState, 'isExpanded')}
+ collapseProjectPhase={collapseProjectPhase}
+ expandProjectPhase={expandProjectPhase}
>
(
@@ -82,6 +85,7 @@ const ProjectStages = ({
phases.map((phase, index) => (
))
}
diff --git a/src/projects/detail/components/WorkInProgress/WorkInProgress.jsx b/src/projects/detail/components/WorkInProgress/WorkInProgress.jsx
index 071c55564..7d98561db 100644
--- a/src/projects/detail/components/WorkInProgress/WorkInProgress.jsx
+++ b/src/projects/detail/components/WorkInProgress/WorkInProgress.jsx
@@ -11,7 +11,7 @@ import ProjectStage from '../ProjectStage'
import './WorkInProgress.scss'
-const WorkInProgress = ({ activePhases, ...props }) => (
+const WorkInProgress = ({ activePhases, phasesStates, ...props }) => (
View all
@@ -19,6 +19,7 @@ const WorkInProgress = ({ activePhases, ...props }) => (
{activePhases.map((activePhase) => (
diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypeCheckpointReview/MilestoneTypeCheckpointReview.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypeCheckpointReview/MilestoneTypeCheckpointReview.jsx
index 01db3a126..d273d9782 100644
--- a/src/projects/detail/components/timeline/milestones/MilestoneTypeCheckpointReview/MilestoneTypeCheckpointReview.jsx
+++ b/src/projects/detail/components/timeline/milestones/MilestoneTypeCheckpointReview/MilestoneTypeCheckpointReview.jsx
@@ -371,11 +371,7 @@ class MilestoneTypeCheckpointReview extends React.Component {
onClick={!currentUser.isCustomer ? this.showCompleteReviewConfirmation : this.completeReview}
disabled={this.shouldDisableCompleteReviewButton(links, selectedLinks) && !isInReview}
>
- Complete review ({
- daysLeft >= 0
- ? `${hoursLeft}h remaining`
- : `${-hoursLeft}h delay`
- })
+ Complete review ({hoursLeft}h)
)}
{!currentUser.isCustomer && extensionRequestButton}
diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypeFinalDesigns/MilestoneTypeFinalDesigns.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypeFinalDesigns/MilestoneTypeFinalDesigns.jsx
index 965247be3..0c3e27231 100644
--- a/src/projects/detail/components/timeline/milestones/MilestoneTypeFinalDesigns/MilestoneTypeFinalDesigns.jsx
+++ b/src/projects/detail/components/timeline/milestones/MilestoneTypeFinalDesigns/MilestoneTypeFinalDesigns.jsx
@@ -394,11 +394,7 @@ class MilestoneTypeFinalDesigns extends React.Component {
onClick={!currentUser.isCustomer ? this.showCompleteReviewConfirmation : this.showCustomerCompleteReviewConfirmation}
disabled={!isInReview}
>
- Complete review ({
- daysLeft >= 0
- ? `${hoursLeft}h remaining`
- : `${-daysLeft}h delay`
- })
+ Complete review ({hoursLeft}h)
)}
{!currentUser.isCustomer && extensionRequestButton}
diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypePhaseSpecification/MilestoneTypePhaseSpecification.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypePhaseSpecification/MilestoneTypePhaseSpecification.jsx
index 64da3dae3..6283a3f41 100644
--- a/src/projects/detail/components/timeline/milestones/MilestoneTypePhaseSpecification/MilestoneTypePhaseSpecification.jsx
+++ b/src/projects/detail/components/timeline/milestones/MilestoneTypePhaseSpecification/MilestoneTypePhaseSpecification.jsx
@@ -100,7 +100,7 @@ class MilestoneTypePhaseSpecification extends React.Component {
onRemoveLink={this.removeUrl}
onUpdateLink={this.updatedUrl}
fields={[{ name: 'url' }]}
- addButtonTitle="Add specification document link"
+ addButtonTitle="Add specification"
formAddTitle="Specification document link"
formAddButtonTitle="Add link"
formUpdateTitle="Editing a link"
diff --git a/src/projects/detail/containers/DashboardContainer.jsx b/src/projects/detail/containers/DashboardContainer.jsx
index e3c76946e..acdce3493 100644
--- a/src/projects/detail/containers/DashboardContainer.jsx
+++ b/src/projects/detail/containers/DashboardContainer.jsx
@@ -14,7 +14,15 @@ import {
filterProjectNotifications,
} from '../../../routes/notifications/helpers/notifications'
import { toggleNotificationRead, toggleBundledNotificationRead } from '../../../routes/notifications/actions'
-import { updateProduct, fireProductDirty, fireProductDirtyUndo, deleteProjectPhase } from '../../actions/project'
+import {
+ updateProduct,
+ fireProductDirty,
+ fireProductDirtyUndo,
+ deleteProjectPhase,
+ expandProjectPhase,
+ collapseProjectPhase,
+ collapseAllProjectPhases,
+} from '../../actions/project'
import { addProductAttachment, updateProductAttachment, removeProductAttachment } from '../../actions/projectAttachment'
import MediaQuery from 'react-responsive'
@@ -54,6 +62,12 @@ class DashboardContainer extends React.Component {
}
}
+ componentWillUnmount() {
+ const { collapseAllProjectPhases } = this.props
+
+ collapseAllProjectPhases()
+ }
+
render() {
const {
project,
@@ -72,9 +86,12 @@ class DashboardContainer extends React.Component {
removeProductAttachment,
deleteProjectPhase,
feeds,
- productsTimelines
+ productsTimelines,
+ phasesStates,
+ expandProjectPhase,
+ collapseProjectPhase,
} = this.props
-
+
// system notifications
const notReadNotifications = filterReadNotifications(notifications.notifications)
const unreadProjectUpdate = filterProjectNotifications(filterNotificationsByProjectId(notReadNotifications, project.id))
@@ -135,6 +152,9 @@ class DashboardContainer extends React.Component {
updateProductAttachment={updateProductAttachment}
removeProductAttachment={removeProductAttachment}
deleteProjectPhase={deleteProjectPhase}
+ phasesStates={phasesStates}
+ expandProjectPhase={expandProjectPhase}
+ collapseProjectPhase={collapseProjectPhase}
/>
}
@@ -155,6 +175,7 @@ const mapStateToProps = ({ notifications, projectState, projectTopics }) => ({
isProcessing: projectState.processing,
phases: projectState.phases,
feeds: projectTopics.feeds[PROJECT_FEED_TYPE_PRIMARY].topics,
+ phasesStates: projectState.phasesStates,
})
const mapDispatchToProps = {
@@ -167,6 +188,9 @@ const mapDispatchToProps = {
updateProductAttachment,
removeProductAttachment,
deleteProjectPhase,
+ expandProjectPhase,
+ collapseProjectPhase,
+ collapseAllProjectPhases,
}
export default connect(mapStateToProps, mapDispatchToProps)(DashboardContainer)
diff --git a/src/projects/detail/containers/ProjectInfoContainer.js b/src/projects/detail/containers/ProjectInfoContainer.js
index ac5fe320e..c563d8862 100644
--- a/src/projects/detail/containers/ProjectInfoContainer.js
+++ b/src/projects/detail/containers/ProjectInfoContainer.js
@@ -8,7 +8,7 @@ import TeamManagementContainer from './TeamManagementContainer'
import { updateProject, deleteProject } from '../../actions/project'
import { setDuration } from '../../../helpers/projectHelper'
import { PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER,
- DIRECT_PROJECT_URL, SALESFORCE_PROJECT_LEAD_LINK, PROJECT_STATUS_CANCELLED } from '../../../config/constants'
+ DIRECT_PROJECT_URL, SALESFORCE_PROJECT_LEAD_LINK, PROJECT_STATUS_CANCELLED, PROJECT_FEED_TYPE_PRIMARY } from '../../../config/constants'
import ProjectInfo from '../../../components/ProjectInfo/ProjectInfo'
class ProjectInfoContainer extends React.Component {
@@ -133,7 +133,7 @@ class ProjectInfoContainer extends React.Component {
const discussions = feeds.map((feed) => ({
title: `${feed.title}`,
- address: `/projects/${project.id}#feed-${feed.id}`,
+ address: feed.tag === PROJECT_FEED_TYPE_PRIMARY ? `/projects/${project.id}#feed-${feed.id}` : `/projects/${project.id}/plan#phase-${feed.phaseId}`,
noNewPage: true,
onClick: onChannelClick ? () => onChannelClick(feed) : null,
isActive: feed.id === activeChannelId,
diff --git a/src/projects/detail/containers/ProjectPlanContainer.jsx b/src/projects/detail/containers/ProjectPlanContainer.jsx
index a7be88c7b..4a8ac9820 100644
--- a/src/projects/detail/containers/ProjectPlanContainer.jsx
+++ b/src/projects/detail/containers/ProjectPlanContainer.jsx
@@ -6,9 +6,18 @@
*/
import React from 'react'
import PT from 'prop-types'
+import _ from 'lodash'
import { connect } from 'react-redux'
-import { updateProduct, fireProductDirty, fireProductDirtyUndo, deleteProjectPhase } from '../../actions/project'
+import {
+ updateProduct,
+ fireProductDirty,
+ fireProductDirtyUndo,
+ deleteProjectPhase,
+ expandProjectPhase,
+ collapseProjectPhase,
+ collapseAllProjectPhases,
+} from '../../actions/project'
import { addProductAttachment, updateProductAttachment, removeProductAttachment } from '../../actions/projectAttachment'
import TwoColsLayout from '../components/TwoColsLayout'
@@ -21,78 +30,116 @@ import { SCREEN_BREAKPOINT_MD, PHASE_STATUS_DRAFT, PROJECT_STATUS_COMPLETED,
PROJECT_STATUS_CANCELLED, PROJECT_FEED_TYPE_PRIMARY, PHASE_STATUS_ACTIVE } from '../../../config/constants'
import Sticky from 'react-stickynode'
import { Link } from 'react-router-dom'
+import { scrollToHash } from '../../../components/ScrollToAnchors'
import './ProjectPlanContainer.scss'
-const ProjectPlanContainer = (props) => {
- const {
- project,
- isSuperUser,
- isManageUser,
- currentMemberRole,
- phases,
- feeds,
- productsTimelines
- } = props
-
- // manager user sees all phases
- // customer user doesn't see unplanned (draft) phases
- const visiblePhases = phases && phases.filter((phase) => (
- isSuperUser || isManageUser || phase.status !== PHASE_STATUS_DRAFT
- ))
-
- const activePhases = phases ? phases.filter((phase) => phase.status === PHASE_STATUS_ACTIVE) : []
-
- const isProjectLive = project.status !== PROJECT_STATUS_COMPLETED && project.status !== PROJECT_STATUS_CANCELLED
-
- const leftArea = (
-
- )
-
- return (
-
-
-
- {(matches) => {
- if (matches) {
- return {leftArea}
- } else {
- return leftArea
- }
- }}
-
-
-
-
- {visiblePhases && visiblePhases.length > 0 ? (
- [
- activePhases.length > 0 && ,
-
- ]
- ) : (
-
- )}
- {isProjectLive && isManageUser && (
- Add New Phase
-
)}
-
-
-
- )
+class ProjectPlanContainer extends React.Component {
+ constructor(props) {
+ super(props)
+
+ this.onChannelClick = this.onChannelClick.bind(this)
+ }
+
+ onChannelClick(topic) {
+ const { expandProjectPhase } = this.props
+ const phaseId = parseInt(topic.tag.replace('phase#', ''), 10)
+
+ expandProjectPhase(phaseId, 'posts')
+ scrollToHash(`phase-${phaseId}`)
+ }
+
+ componentWillUnmount() {
+ const { collapseAllProjectPhases } = this.props
+
+ collapseAllProjectPhases()
+ }
+
+ render() {
+ const {
+ project,
+ isSuperUser,
+ isManageUser,
+ currentMemberRole,
+ phases,
+ productsTimelines,
+ phasesTopics,
+ } = this.props
+
+ // manager user sees all phases
+ // customer user doesn't see unplanned (draft) phases
+ const visiblePhases = phases && phases.filter((phase) => (
+ isSuperUser || isManageUser || phase.status !== PHASE_STATUS_DRAFT
+ ))
+
+ const activePhases = phases ? phases.filter((phase) => phase.status === PHASE_STATUS_ACTIVE) : []
+
+ const isProjectLive = project.status !== PROJECT_STATUS_COMPLETED && project.status !== PROJECT_STATUS_CANCELLED
+ // get list of phase topic in same order as phases
+ const topics = _.compact(
+ visiblePhases.map((phase) => {
+ const topic = _.get(phasesTopics, `[${phase.id}].topic`)
+
+ if (!topic) {
+ return null
+ }
+
+ return ({
+ ...topic,
+ phaseId: phase.id,
+ })
+ })
+ )
+
+ const leftArea = (
+
+ )
+
+ return (
+
+
+
+ {(matches) => {
+ if (matches) {
+ return {leftArea}
+ } else {
+ return leftArea
+ }
+ }}
+
+
+
+
+ {visiblePhases && visiblePhases.length > 0 ? (
+ [
+ activePhases.length > 0 && ,
+
+ ]
+ ) : (
+
+ )}
+ {isProjectLive && isManageUser && (
+ Add New Phase
+
)}
+
+
+
+ )
+ }
}
ProjectPlanContainer.propTypes = {
@@ -106,10 +153,12 @@ ProjectPlanContainer.propTypes = {
productsTimelines: PT.object.isRequired,
}
-const mapStateToProps = ({ projectState, projectTopics }) => ({
+const mapStateToProps = ({ projectState, projectTopics, phasesTopics }) => ({
productTemplates: projectState.allProductTemplates,
phases: projectState.phases,
feeds: projectTopics.feeds[PROJECT_FEED_TYPE_PRIMARY].topics,
+ phasesTopics,
+ phasesStates: projectState.phasesStates,
})
const mapDispatchToProps = {
@@ -120,6 +169,9 @@ const mapDispatchToProps = {
updateProductAttachment,
removeProductAttachment,
deleteProjectPhase,
+ expandProjectPhase,
+ collapseProjectPhase,
+ collapseAllProjectPhases,
}
export default connect(mapStateToProps, mapDispatchToProps)(ProjectPlanContainer)
diff --git a/src/projects/reducers/project.js b/src/projects/reducers/project.js
index ac9d92894..0606421c8 100644
--- a/src/projects/reducers/project.js
+++ b/src/projects/reducers/project.js
@@ -15,7 +15,8 @@ import {
GET_PROJECTS_SUCCESS, PROJECT_DIRTY, PROJECT_DIRTY_UNDO, LOAD_PROJECT_PHASES_SUCCESS, LOAD_PROJECT_PHASES_PENDING,
LOAD_PROJECT_TEMPLATE_SUCCESS, LOAD_PROJECT_PRODUCT_TEMPLATES_SUCCESS, LOAD_ALL_PRODUCT_TEMPLATES_SUCCESS, PRODUCT_DIRTY, PRODUCT_DIRTY_UNDO,
UPDATE_PRODUCT_FAILURE, UPDATE_PRODUCT_SUCCESS, UPDATE_PHASE_SUCCESS, UPDATE_PHASE_PENDING, UPDATE_PHASE_FAILURE,
- DELETE_PROJECT_PHASE_PENDING, DELETE_PROJECT_PHASE_SUCCESS, DELETE_PROJECT_PHASE_FAILURE, PHASE_DIRTY_UNDO, PHASE_DIRTY
+ DELETE_PROJECT_PHASE_PENDING, DELETE_PROJECT_PHASE_SUCCESS, DELETE_PROJECT_PHASE_FAILURE, PHASE_DIRTY_UNDO, PHASE_DIRTY,
+ EXPAND_PROJECT_PHASE, COLLAPSE_PROJECT_PHASE, COLLAPSE_ALL_PROJECT_PHASES,
} from '../../config/constants'
import _ from 'lodash'
import update from 'react-addons-update'
@@ -34,7 +35,8 @@ const initialState = {
allProductTemplates: [],
phases: null,
phasesNonDirty: null,
- isLoadingPhases: false
+ isLoadingPhases: false,
+ phasesStates: {} // controls opened phases and tabs of the phases
}
// NOTE: We should always update projectNonDirty state whenever we update the project state
@@ -98,6 +100,49 @@ function getProductInPhases(phases, phaseId, productId) {
export const projectState = function (state=initialState, action) {
switch (action.type) {
+ case EXPAND_PROJECT_PHASE: {
+ const { phaseId, tab } = action.payload
+ const currentPhaseTab = state.phasesStates[phaseId] || {}
+ const updatedPhaseTab = {
+ ...currentPhaseTab,
+ isExpanded: true
+ }
+ if (tab) {
+ updatedPhaseTab.tab = tab
+ }
+
+ return {
+ ...state,
+ phasesStates: {
+ ...state.phasesStates,
+ [phaseId]: updatedPhaseTab
+ }
+ }
+ }
+
+ case COLLAPSE_PROJECT_PHASE: {
+ const { phaseId } = action.payload
+ const currentPhaseTab = state.phasesStates[phaseId] || {}
+ const updatedPhaseTab = {
+ ...currentPhaseTab,
+ isExpanded: false
+ }
+
+ return {
+ ...state,
+ phasesStates: {
+ ...state.phasesStates,
+ [phaseId]: updatedPhaseTab
+ }
+ }
+ }
+
+ case COLLAPSE_ALL_PROJECT_PHASES:
+ return {
+ ...state,
+ phasesStates: {},
+ }
+
case LOAD_PROJECT_PENDING:
return Object.assign({}, state, {
isLoading: true,