diff --git a/src/components/ScrollToAnchors.jsx b/src/components/ScrollToAnchors.jsx
index 19058226c..90ba4db22 100644
--- a/src/components/ScrollToAnchors.jsx
+++ b/src/components/ScrollToAnchors.jsx
@@ -43,7 +43,7 @@ export function scrollToAnchors(Component) {
componentDidMount() {
const { hash } = window.location
- if (hash !== '') {
+ if (!this.props.disableAutoScrolling && hash !== '') {
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
diff --git a/src/projects/actions/projectDashboard.js b/src/projects/actions/projectDashboard.js
index d20494ed1..131e3706b 100644
--- a/src/projects/actions/projectDashboard.js
+++ b/src/projects/actions/projectDashboard.js
@@ -1,15 +1,9 @@
import _ from 'lodash'
import { loadMembers } from '../../actions/members'
-import { loadProject, loadProjectInvite, loadDirectProjectData, loadProjectPhasesWithProducts } from './project'
+import { loadProject, loadProjectInvite, loadDirectProjectData } from './project'
+import { loadProjectPlan } from './projectPlan'
import { loadProjectsMetadata } from '../../actions/templates'
-import { loadProductTimelineWithMilestones } from './productsTimelines'
-import { loadFeedsForPhases } from './phasesTopics'
-import { LOAD_PROJECT_DASHBOARD,
- LOAD_ADDITIONAL_PROJECT_DATA,
- DISCOURSE_BOT_USERID,
- CODER_BOT_USERID,
- TC_SYSTEM_USERID
-} from '../../config/constants'
+import { LOAD_PROJECT_DASHBOARD, LOAD_ADDITIONAL_PROJECT_DATA } from '../../config/constants'
/**
* Load all project data to paint the dashboard
@@ -44,28 +38,7 @@ const getDashboardData = (dispatch, getState, projectId, isOnlyLoadProjectInfo)
// for new projects load phases, products, project template and product templates
if (project.version === 'v3') {
promises.push(
- dispatch(loadProjectPhasesWithProducts(projectId))
- .then(({ value: phases }) => {
- loadFeedsForPhases(projectId, phases, dispatch)
- .then((phaseFeeds) => {
- let phaseUserIds = []
- _.forEach(phaseFeeds, phaseFeed => {
- phaseUserIds = _.union(phaseUserIds, _.map(phaseFeed.topics, 'userId'))
- _.forEach(phaseFeed.topics, topic => {
- phaseUserIds = _.union(phaseUserIds, _.map(topic.posts, 'userId'))
- })
- // this is to remove any nulls from the list (dev had some bad data)
- _.remove(phaseUserIds, i => !i || [DISCOURSE_BOT_USERID, CODER_BOT_USERID, TC_SYSTEM_USERID].indexOf(i) > -1)
- })
- // take difference of userIds identified from project members
- phaseUserIds = _.difference(phaseUserIds, userIds)
-
- dispatch(loadMembers(phaseUserIds))
- })
- // load timelines for phase products here together with all dashboard data
- // as we need to know timeline data not only inside timeline container
- loadTimelinesForPhasesProducts(phases, dispatch)
- })
+ dispatch(loadProjectPlan(projectId, userIds))
)
}
@@ -96,26 +69,6 @@ const getData = (dispatch, getState, projectId, isOnlyLoadProjectInfo) => {
.catch(() => getDashboardData(dispatch, getState, projectId, isOnlyLoadProjectInfo))
}
-/**
- * Load timelines for phase's products
- *
- * @param {Array} phases list of phases
- * @param {Function} dispatch dispatch function
- */
-function loadTimelinesForPhasesProducts(phases, dispatch) {
- const products = []
-
- phases.forEach((phase) => {
- phase.products.forEach((product) => {
- products.push(product)
- })
- })
-
- return Promise.all(
- products.map((product) => dispatch(loadProductTimelineWithMilestones(product.id)))
- )
-}
-
export function loadProjectDashboard(projectId, isOnlyLoadProjectInfo = false) {
return (dispatch, getState) => {
return dispatch({
diff --git a/src/projects/actions/projectPlan.js b/src/projects/actions/projectPlan.js
new file mode 100644
index 000000000..71707f9da
--- /dev/null
+++ b/src/projects/actions/projectPlan.js
@@ -0,0 +1,58 @@
+import _ from 'lodash'
+import { loadMembers } from '../../actions/members'
+import { loadProjectPhasesWithProducts } from './project'
+import { loadFeedsForPhases } from './phasesTopics'
+import { loadProductTimelineWithMilestones } from './productsTimelines'
+import {
+ DISCOURSE_BOT_USERID,
+ CODER_BOT_USERID,
+ TC_SYSTEM_USERID
+} from '../../config/constants'
+
+
+/**
+ * Load timelines for phase's products
+ *
+ * @param {Array} phases list of phases
+ * @param {Function} dispatch dispatch function
+ */
+function loadTimelinesForPhasesProducts(phases, dispatch) {
+ const products = []
+
+ phases.forEach((phase) => {
+ phase.products.forEach((product) => {
+ products.push(product)
+ })
+ })
+
+ return Promise.all(
+ products.map((product) => dispatch(loadProductTimelineWithMilestones(product.id)))
+ )
+}
+
+export function loadProjectPlan(projectId, existingUserIds) {
+ return (dispatch) => {
+ return dispatch(loadProjectPhasesWithProducts(projectId))
+ .then(({ value: phases }) => {
+ loadFeedsForPhases(projectId, phases, dispatch)
+ .then((phaseFeeds) => {
+ let phaseUserIds = []
+ _.forEach(phaseFeeds, phaseFeed => {
+ phaseUserIds = _.union(phaseUserIds, _.map(phaseFeed.topics, 'userId'))
+ _.forEach(phaseFeed.topics, topic => {
+ phaseUserIds = _.union(phaseUserIds, _.map(topic.posts, 'userId'))
+ })
+ // this is to remove any nulls from the list (dev had some bad data)
+ _.remove(phaseUserIds, i => !i || [DISCOURSE_BOT_USERID, CODER_BOT_USERID, TC_SYSTEM_USERID].indexOf(i) > -1)
+ })
+ // take difference of existingUserIds identified from project members
+ phaseUserIds = _.difference(phaseUserIds, existingUserIds)
+
+ dispatch(loadMembers(phaseUserIds))
+ })
+ // load timelines for phase products here together with all dashboard data
+ // as we need to know timeline data not only inside timeline container
+ loadTimelinesForPhasesProducts(phases, dispatch)
+ })
+ }
+}
diff --git a/src/projects/detail/ProjectDetail.jsx b/src/projects/detail/ProjectDetail.jsx
index 1191dc1f6..ae164f9eb 100644
--- a/src/projects/detail/ProjectDetail.jsx
+++ b/src/projects/detail/ProjectDetail.jsx
@@ -64,9 +64,6 @@ const ProjectDetailView = (props) => {
if (!currentMemberRole && props.currentUserRoles && props.currentUserRoles.length > 0) {
currentMemberRole = props.currentUserRoles[0]
}
- const regex = /#(feed-([0-9]+)|comment-([0-9]+))/
- const match = props.location.hash.match(regex)
- const ids = match ? { feedId: match[2], commentId: match[3] } : {}
const { component: Component } = props
const componentProps = {
@@ -79,7 +76,7 @@ const ProjectDetailView = (props) => {
isProcessing: props.isProcessing,
allProductTemplates: props.allProductTemplates,
productsTimelines: props.productsTimelines,
- ...ids
+ location: props.location,
}
return
}
diff --git a/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx b/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx
index 9f6bb7eeb..584045f7b 100644
--- a/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx
+++ b/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx
@@ -268,7 +268,7 @@ class EditProjectForm extends Component {
render() {
- const { isEdittable, showHidden, productTemplates, productCategories } = this.props
+ const { isEdittable, showHidden, productTemplates, productCategories, disableAutoScrolling } = this.props
const { template } = this.state
const { project, dirtyProject } = this.state
const onLeaveMessage = this.onLeave() || ''
@@ -287,6 +287,7 @@ class EditProjectForm extends Component {
sectionNumber={idx + 1}
resetFeatures={this.onFeaturesSaveAttachedClick}
showFeaturesDialog={this.showFeaturesDialog}
+ disableAutoScrolling={disableAutoScrolling}
// TODO we shoudl not update the props (section is coming from props)
validate={(isInvalid) => section.isInvalid = isInvalid}
showHidden={showHidden}
@@ -353,7 +354,8 @@ class EditProjectForm extends Component {
}
EditProjectForm.defaultProps = {
- shouldUpdateTemplate: false
+ shouldUpdateTemplate: false,
+ disableAutoScrolling: false,
}
EditProjectForm.propTypes = {
@@ -371,6 +373,7 @@ EditProjectForm.propTypes = {
updateAttachment: PropTypes.func.isRequired,
removeAttachment: PropTypes.func.isRequired,
shouldUpdateTemplate: PropTypes.bool,
+ disableAutoScrolling: PropTypes.bool,
}
export default EditProjectForm
diff --git a/src/projects/detail/components/PhaseFeed/PhaseFeed.jsx b/src/projects/detail/components/PhaseFeed/PhaseFeed.jsx
index 1fbeb0bc7..0269568ff 100644
--- a/src/projects/detail/components/PhaseFeed/PhaseFeed.jsx
+++ b/src/projects/detail/components/PhaseFeed/PhaseFeed.jsx
@@ -5,26 +5,49 @@
*/
import React from 'react'
import _ from 'lodash'
+import { withRouter } from 'react-router-dom'
import ScrollableFeed from '../../../../components/Feed/ScrollableFeed'
import spinnerWhileLoading from '../../../../components/LoadingSpinner'
+import { scrollToHash } from '../../../../components/ScrollToAnchors'
import './PhaseFeed.scss'
-const PhaseFeedView = (props) => (
-
-
-
-)
+class PhaseFeedView extends React.Component {
+ constructor(props) {
+ super(props)
+ }
+
+ componentDidMount() {
+ !_.isEmpty(this.props.location.hash) && this.handleUrlHash(this.props)
+ }
+
+ // when the phase feed is actually loaded/rendered scroll to the appropriate post depending on url hash
+ handleUrlHash(props) {
+ const hashParts = _.split(location.hash.substring(1), '-')
+ const phaseId = hashParts[0] === 'phase' ? parseInt(hashParts[1], 10) : null
+ if (phaseId === props.phaseId) {
+ setTimeout(() => scrollToHash(props.location.hash), 100)
+ }
+ }
+
+ render() {
+ return (
+
+
+
+ )
+ }
+}
const enhance = spinnerWhileLoading(props => !props.isLoading)
-const EnhancedPhaseFeedView = enhance(PhaseFeedView)
+const EnhancedPhaseFeedView = enhance(withRouter(PhaseFeedView))
export default EnhancedPhaseFeedView
diff --git a/src/projects/detail/components/ProjectStage.jsx b/src/projects/detail/components/ProjectStage.jsx
index fea286765..96df9bfb3 100644
--- a/src/projects/detail/components/ProjectStage.jsx
+++ b/src/projects/detail/components/ProjectStage.jsx
@@ -5,6 +5,7 @@ import React from 'react'
import PT from 'prop-types'
import _ from 'lodash'
import uncontrollable from 'uncontrollable'
+import { withRouter } from 'react-router-dom'
import { formatNumberWithCommas } from '../../../helpers/format'
import { getPhaseActualData } from '../../../helpers/projectHelper'
@@ -22,7 +23,6 @@ import ProductTimelineContainer from '../containers/ProductTimelineContainer'
import NotificationsReader from '../../../components/NotificationsReader'
import { phaseFeedHOC } from '../containers/PhaseFeedHOC'
import spinnerWhileLoading from '../../../components/LoadingSpinner'
-import { scrollToHash } from '../../../components/ScrollToAnchors'
const enhance = spinnerWhileLoading(props => !props.processing)
const EnhancedEditProjectForm = enhance(EditProjectForm)
@@ -130,23 +130,28 @@ class ProjectStage extends React.Component{
expandProjectPhase(phase.id, tab)
}
- componentDidUpdate() {
- const { phaseState } = this.props
- if (_.get(phaseState, 'isExpanded')) {
- const scrollTo = window.location.hash ? window.location.hash.substring(1) : null
- if (scrollTo) {
- scrollToHash(scrollTo)
- }
- }
+ componentDidMount() {
+ !_.isEmpty(this.props.location.hash) && this.handleUrlHash(this.props)
}
- componentWillReceiveProps(nextProps) {
- const { feedId, commentId, phase, phaseState, expandProjectPhase } = this.props
- const { feed } = nextProps
- if (!_.get(phaseState, 'isExpanded') && feed && (feed.id === parseInt(feedId) || feed.postIds.includes(parseInt(commentId)))){
- expandProjectPhase(phase.id, 'posts')
+ componentDidUpdate(prevProps) {
+ const { location } = this.props
+ if (!_.isEmpty(location.hash) && location.hash !== prevProps.location.hash) {
+ this.handleUrlHash(this.props)
}
+ }
+
+ // expand a phase if necessary depending on the url hash
+ handleUrlHash(props) {
+ const { expandProjectPhase, phase, location } = props
+ const hashParts = _.split(location.hash.substring(1), '-')
+ const phaseId = hashParts[0] === 'phase' ? parseInt(hashParts[1], 10) : null
+
+ if (phaseId && phase.id === phaseId) {
+ const tab = hashParts[2]
+ expandProjectPhase(phaseId, tab)
+ }
}
render() {
@@ -169,6 +174,7 @@ class ProjectStage extends React.Component{
collapseProjectPhase,
expandProjectPhase,
commentAnchorPrefix,
+ isLoading,
// comes from phaseFeedHOC
currentUser,
@@ -253,6 +259,8 @@ class ProjectStage extends React.Component{
projectMembers={projectMembers}
onSaveMessage={onSaveMessage}
commentAnchorPrefix={commentAnchorPrefix}
+ phaseId={phase.id}
+ isLoading={isLoading}
/>
)}
@@ -278,6 +286,7 @@ class ProjectStage extends React.Component{
removeAttachment={this.removeProductAttachment}
attachmentsStorePath={attachmentsStorePath}
canManageAttachments={!!currentMemberRole}
+ disableAutoScrolling
/>
}
@@ -311,7 +320,7 @@ ProjectStage.propTypes = {
commentAnchorPrefix: PT.string,
}
-const ProjectStageUncontrollable = uncontrollable(ProjectStage, {
+const ProjectStageUncontrollable = uncontrollable(withRouter(ProjectStage), {
activeTab: 'onTabClick',
})
diff --git a/src/projects/detail/components/ProjectStages.jsx b/src/projects/detail/components/ProjectStages.jsx
index 744a368f9..595cf0aa3 100644
--- a/src/projects/detail/components/ProjectStages.jsx
+++ b/src/projects/detail/components/ProjectStages.jsx
@@ -10,6 +10,7 @@ import { withRouter } from 'react-router-dom'
import { formatNumberWithCommas } from '../../../helpers/format'
import { getPhaseActualData } from '../../../helpers/projectHelper'
+import spinnerWhileLoading from '../../../components/LoadingSpinner'
import Section from '../components/Section'
import ProjectStage from '../components/ProjectStage'
import PhaseCardListHeader from '../components/PhaseCardListHeader'
@@ -93,6 +94,7 @@ const ProjectStages = ({
collapseProjectPhase,
feedId,
commentId,
+ location
}) => (
@@ -100,6 +102,7 @@ const ProjectStages = ({
{
phases.map((phase, index) => (
!props.isLoadingPhases)
+const EnhancedProjectStages = enhance(ProjectStages)
+
+export default withRouter(EnhancedProjectStages)
diff --git a/src/projects/detail/components/SpecSection.jsx b/src/projects/detail/components/SpecSection.jsx
index da9efb25e..ed278e4b5 100644
--- a/src/projects/detail/components/SpecSection.jsx
+++ b/src/projects/detail/components/SpecSection.jsx
@@ -425,6 +425,7 @@ SpecSection.propTypes = {
updateAttachment: PropTypes.func,
removeAttachment: PropTypes.func,
productCategories: PropTypes.array.isRequired,
+ disableAutoScrolling: PropTypes.bool,
}
export default scrollToAnchors(SpecSection)
diff --git a/src/projects/detail/containers/DashboardContainer.jsx b/src/projects/detail/containers/DashboardContainer.jsx
index a0ffb55df..3405f36d7 100644
--- a/src/projects/detail/containers/DashboardContainer.jsx
+++ b/src/projects/detail/containers/DashboardContainer.jsx
@@ -112,6 +112,7 @@ class DashboardContainer extends React.Component {
phasesTopics,
expandProjectPhase,
collapseProjectPhase,
+ location,
} = this.props
// system notifications
@@ -125,6 +126,7 @@ class DashboardContainer extends React.Component {
const leftArea = (
-1, resetNewComment, prevProps)
}).filter(item => item)
- }, () => {
- if (prevProps) {
- // only scroll at first time
- return
- }
- const scrollTo = window.location.hash ? window.location.hash.substring(1) : null
- if (scrollTo) {
- scrollToHash(scrollTo)
- }
})
}
diff --git a/src/projects/detail/containers/PhaseFeedHOC.jsx b/src/projects/detail/containers/PhaseFeedHOC.jsx
index 74e1127d0..d7f957d65 100644
--- a/src/projects/detail/containers/PhaseFeedHOC.jsx
+++ b/src/projects/detail/containers/PhaseFeedHOC.jsx
@@ -43,9 +43,9 @@ const phaseFeedHOC = (Component) => {
}
componentWillMount() {
- const { isLoading, loadPhaseFeed, phase, project } = this.props
+ const { isLoading, topic, loadPhaseFeed, phase, project } = this.props
- if (!isLoading) {
+ if (!isLoading && !topic) {
loadPhaseFeed(project.id, phase.id)
}
}
diff --git a/src/projects/detail/containers/ProjectInfoContainer.js b/src/projects/detail/containers/ProjectInfoContainer.js
index 168de6d5f..bca064cf8 100644
--- a/src/projects/detail/containers/ProjectInfoContainer.js
+++ b/src/projects/detail/containers/ProjectInfoContainer.js
@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
import update from 'react-addons-update'
import _ from 'lodash'
import LinksMenu from '../../../components/LinksMenu/LinksMenu'
@@ -9,6 +10,7 @@ import TeamManagementContainer from './TeamManagementContainer'
import { updateProject, deleteProject } from '../../actions/project'
import { loadDashboardFeeds, loadProjectMessages } from '../../actions/projectTopics'
import { loadPhaseFeed } from '../../actions/phasesTopics'
+import { loadProjectPlan } from '../../actions/projectPlan'
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, PROJECT_ATTACHMENTS_FOLDER,
@@ -68,16 +70,14 @@ class ProjectInfoContainer extends React.Component {
}
componentWillMount() {
- const { project, isFeedsLoading, feeds, loadDashboardFeeds,
- loadProjectMessages, phases, phasesTopics, loadPhaseFeed, canAccessPrivatePosts } = this.props
+ const { project, isFeedsLoading, feeds, phases, phasesTopics, loadPhaseFeed, location } = this.props
this.setDuration(project)
// load feeds from dashboard if they are not currently loading or loaded yet
// also it will load feeds, if we already loaded them, but it was 0 feeds before
if (!isFeedsLoading && feeds.length < 1) {
- loadDashboardFeeds(project.id)
- canAccessPrivatePosts && loadProjectMessages(project.id)
+ this.loadAllFeeds()
}
// load phases feeds if they are not loaded yet
@@ -87,10 +87,80 @@ class ProjectInfoContainer extends React.Component {
loadPhaseFeed(project.id, phase.id)
}
})
+
+ // handle url hash
+ if (!_.isEmpty(location.hash)) {
+ this.handleUrlHash(this.props)
+ }
}
- componentWillReceiveProps({project}) {
+ componentWillReceiveProps(props) {
+ const { project, location } = props
+
this.setDuration(project)
+
+ if (!_.isEmpty(location.hash) && location.hash !== this.props.location.hash) {
+ this.handleUrlHash(props)
+ }
+ }
+
+ // this is just to see if the comment/feed/post/phase the url hash is attempting to scroll to is loaded or not
+ // if its not loaded then we load the appropriate item
+ handleUrlHash(props) {
+ const { project, isFeedsLoading, phases, phasesTopics, feeds, loadProjectPlan, loadPhaseFeed, location } = props
+ const hashParts = _.split(location.hash.substring(1), '-')
+ const hashPrimaryId = parseInt(hashParts[1], 10)
+
+ switch (hashParts[0]) {
+ case 'comment': {
+ if (!isFeedsLoading && !this.foundCommentInFeeds(feeds, hashPrimaryId)) {
+ this.loadAllFeeds()
+ }
+ break
+ }
+
+ case 'feed': {
+ if (!isFeedsLoading && !_.some(feeds, { id: hashPrimaryId})) {
+ this.loadAllFeeds()
+ }
+ break
+ }
+
+ case 'phase': {
+ const postId = parseInt(hashParts[3], 10)
+
+ if (phases && phasesTopics) {
+ if (!_.some(phases, { id: hashPrimaryId})) {
+ let existingUserIds = _.map(project.members, 'userId')
+ existingUserIds= _.union(existingUserIds, _.map(project.invites, 'userId'))
+ loadProjectPlan(project.id, existingUserIds)
+ } else if(postId && !(phasesTopics[hashPrimaryId].topic && phasesTopics[hashPrimaryId].topic.postIds.includes(postId))) {
+ loadPhaseFeed(project.id, hashPrimaryId)
+ }
+ }
+ break
+ }
+ }
+ }
+
+ loadAllFeeds() {
+ const { canAccessPrivatePosts, loadDashboardFeeds, loadProjectMessages, project } = this.props
+
+ loadDashboardFeeds(project.id)
+ canAccessPrivatePosts && loadProjectMessages(project.id)
+ }
+
+ foundCommentInFeeds(feeds, commentId) {
+ let commentFound = false
+
+ _.forEach(feeds, feed => _.forEach(feed.posts, post => {
+ if (post.id === commentId) {
+ commentFound = true
+ return false
+ }
+ }))
+
+ return commentFound
}
onChangeStatus(projectId, status, reason) {
@@ -437,7 +507,6 @@ class ProjectInfoContainer extends React.Component {
...this.extractAttachmentLinksFromPosts(feeds),
...this.extractAttachmentLinksFromPosts(phaseFeeds)
]
- console.dir(attachments)
return (
@@ -538,6 +607,6 @@ const mapStateToProps = ({ templates, projectState, members, loadUser }) => {
const mapDispatchToProps = { updateProject, deleteProject, addProjectAttachment, updateProjectAttachment,
loadProjectMessages, discardAttachments, uploadProjectAttachments, loadDashboardFeeds, loadPhaseFeed, changeAttachmentPermission,
- removeProjectAttachment, saveFeedComment }
+ removeProjectAttachment, loadProjectPlan, saveFeedComment }
-export default connect(mapStateToProps, mapDispatchToProps)(ProjectInfoContainer)
+export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ProjectInfoContainer))
diff --git a/src/projects/detail/containers/ProjectPlanContainer.jsx b/src/projects/detail/containers/ProjectPlanContainer.jsx
index 460530cbd..f9bbb0147 100644
--- a/src/projects/detail/containers/ProjectPlanContainer.jsx
+++ b/src/projects/detail/containers/ProjectPlanContainer.jsx
@@ -7,6 +7,7 @@
import React from 'react'
import PT from 'prop-types'
import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
import _ from 'lodash'
import {
@@ -63,27 +64,16 @@ class ProjectPlanContainer extends React.Component {
}
componentDidMount() {
- const { expandProjectPhase } = this.props
- const scrollTo = window.location.hash ? window.location.hash.substring(1) : null
- if (scrollTo) {
- const hashParts = _.split(scrollTo, '-')
- const phaseId = hashParts[0] === 'phase' ? parseInt(hashParts[1], 10) : null
- if (phaseId) {
- let tab = hashParts[2]
- tab = tab === scrollTo ? 'timeline' : tab
- // we just open tab, while smooth scrolling has to be caused by URL hash
- expandProjectPhase(phaseId, tab)
- }
- } else {
- // if the user is a customer and its not a direct link to a particular phase
- // then by default expand all phases which are active
- if (this.props.isCustomerUser) {
- _.forEach(this.props.phases, phase => {
- if (phase.status === PHASE_STATUS_ACTIVE) {
- expandProjectPhase(phase.id)
- }
- })
- }
+ const { expandProjectPhase, location } = this.props
+
+ // if the user is a customer and its not a direct link to a particular phase
+ // then by default expand all phases which are active
+ if (_.isEmpty(location.hash) && this.props.isCustomerUser) {
+ _.forEach(this.props.phases, phase => {
+ if (phase.status === PHASE_STATUS_ACTIVE) {
+ expandProjectPhase(phase.id)
+ }
+ })
}
}
@@ -106,6 +96,8 @@ class ProjectPlanContainer extends React.Component {
productsTimelines,
phasesTopics,
isProcessing,
+ isLoadingPhases,
+ location
} = this.props
// manager user sees all phases
@@ -123,6 +115,7 @@ class ProjectPlanContainer extends React.Component {
const leftArea = (
)}
- {isProjectLive && checkPermission(PERMISSIONS.EDIT_PROJECT_PLAN, project, phases) && (
+ {isProjectLive && checkPermission(PERMISSIONS.EDIT_PROJECT_PLAN, project, phases) && !isLoadingPhases && (
Add New Phase
)}
@@ -208,6 +201,7 @@ const mapStateToProps = ({ projectState, projectTopics, phasesTopics, templates
isFeedsLoading: projectTopics.isLoading,
phasesTopics,
phasesStates: projectState.phasesStates,
+ isLoadingPhases: projectState.isLoadingPhases,
}
}
@@ -224,4 +218,4 @@ const mapDispatchToProps = {
collapseAllProjectPhases,
}
-export default connect(mapStateToProps, mapDispatchToProps)(ProjectPlanContainer)
+export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ProjectPlanContainer))
diff --git a/src/projects/detail/containers/SingleFeedContainer.jsx b/src/projects/detail/containers/SingleFeedContainer.jsx
index 844b9671c..84f4687fa 100644
--- a/src/projects/detail/containers/SingleFeedContainer.jsx
+++ b/src/projects/detail/containers/SingleFeedContainer.jsx
@@ -14,6 +14,7 @@ import React from 'react'
import _ from 'lodash'
import ScrollableFeed from '../../../components/Feed/ScrollableFeed'
+import { scrollToHash } from '../../../components/ScrollToAnchors'
const bindMethods = [
'onNewCommentChange',
@@ -43,6 +44,22 @@ class SingleFeedContainer extends React.Component {
})
}
+ componentDidMount() {
+ // we use this to just scroll to a feed block or comment in a feed block,
+ // only if there is a url hash and the feed id and comment id match this feed
+ const scrollTo = window.location.hash ? window.location.hash.substring(1) : null
+ if (scrollTo) {
+ const hashParts = _.split(scrollTo, '-')
+ const hashId = parseInt(hashParts[1], 10)
+ if (
+ (hashParts[0] === 'feed' && hashId === this.props.id) ||
+ (hashParts[0] === 'comment' && this.props.comments && _.some(this.props.comments, { id: hashId }))
+ ) {
+ setTimeout(() => scrollToHash(scrollTo), 100)
+ }
+ }
+ }
+
render() {
const nonBindProps = _.omit(this.props, bindMethods)
const bindProps = _.zipObject(bindMethods, bindMethods.map((method) => this[method]))