diff --git a/src/api/projects.js b/src/api/projects.js index 3510ca29d..c5dba1524 100644 --- a/src/api/projects.js +++ b/src/api/projects.js @@ -4,7 +4,7 @@ import { TC_API_URL, PROJECTS_API_URL, PROJECTS_LIST_PER_PAGE } from '../config/ export function getProjects(criteria, pageNum) { // add default params - const includeFields = ['id', 'name', 'description', 'members', 'status', 'type', 'actualPrice', 'estimatedPrice', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'details', 'lastActivityAt', 'lastActivityUserId', 'version'] + const includeFields = ['id', 'name', 'description', 'members', 'status', 'type', 'actualPrice', 'estimatedPrice', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'details', 'lastActivityAt', 'lastActivityUserId', 'version', 'templateId'] const params = { limit: PROJECTS_LIST_PER_PAGE, offset: (pageNum - 1) * PROJECTS_LIST_PER_PAGE, diff --git a/src/components/NotificationsDropdown/NotificationsDropdown.jsx b/src/components/NotificationsDropdown/NotificationsDropdown.jsx index a065ca9e0..18aa7d634 100644 --- a/src/components/NotificationsDropdown/NotificationsDropdown.jsx +++ b/src/components/NotificationsDropdown/NotificationsDropdown.jsx @@ -9,25 +9,48 @@ import EnhancedDropdown from './EnhancedDropdown' import NotificationsBell from './NotificationsBell' -const NotificationsDropdown = (props) => { - return ( -
- -
- -
-
-
- {props.children} +class NotificationsDropdown extends React.Component { + + constructor(props) { + super(props) + this.state = { + isOpen: false + } + + this.toggle = this.toggle.bind(this) + } + + toggle(isOpen) { + if (typeof isOpen === 'object') { + this.props.onToggle(!this.state.isOpen) + this.setState({ isOpen: !this.state.isOpen}) + } else { + this.props.onToggle(isOpen) + this.setState({ isOpen }) + } + } + + render() { + const { hasUnread, hasNew, children } = this.props + return ( +
+ +
+ +
+
+
+ {children} +
-
- -
- ) + +
+ ) + } } NotificationsDropdown.propTypes = { diff --git a/src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx b/src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx index c086ffc3a..c532881fe 100644 --- a/src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx +++ b/src/components/NotificationsDropdown/NotificationsDropdownContainer.jsx @@ -8,11 +8,12 @@ import { Link } from 'react-router-dom' import { connect } from 'react-redux' import _ from 'lodash' import { TransitionGroup, Transition } from 'react-transition-group' -import { getNotifications, toggleNotificationSeen, markAllNotificationsRead, toggleNotificationRead, visitNotifications, +import { getNotifications, toggleNotificationSeen, markAllNotificationsRead, markAllNotificationsSeen, toggleNotificationRead, toggleBundledNotificationRead, viewOlderNotifications, hideOlderNotifications } from '../../routes/notifications/actions' import { splitNotificationsBySources, filterReadNotifications, + filterSeenNotifications, limitQuantityInSources, preRenderNotifications, } from '../../routes/notifications/helpers/notifications' @@ -29,9 +30,9 @@ import { NOTIFICATIONS_DROPDOWN_PER_SOURCE, NOTIFICATIONS_NEW_PER_SOURCE, REFRES import './NotificationsDropdown.scss' const NotificationsDropdownContainerView = (props) => { - const {initialized, isLoading, lastVisited, sources, notifications, markAllNotificationsRead, toggleNotificationRead, toggleNotificationSeen, - pending, toggleBundledNotificationRead, visitNotifications, oldSourceIds, viewOlderNotifications, isDropdownMobileOpen, isDropdownWebOpen, - toggleNotificationsDropdownMobile, toggleNotificationsDropdownWeb } = props + const {initialized, isLoading, sources, notifications, markAllNotificationsRead, toggleNotificationRead, toggleNotificationSeen, + pending, toggleBundledNotificationRead, oldSourceIds, viewOlderNotifications, isDropdownMobileOpen, isDropdownWebOpen, + toggleNotificationsDropdownMobile, toggleNotificationsDropdownWeb, markAllNotificationsSeen } = props if (!initialized && isLoading) { return ( @@ -51,9 +52,11 @@ const NotificationsDropdownContainerView = (props) => { } const notReadNotifications = filterReadNotifications(notifications) + const notSeenNotifications = filterSeenNotifications(notifications) const allNotificationsBySources = splitNotificationsBySources(sources, notReadNotifications) const hasUnread = notReadNotifications.length > 0 + const hasUnseen = notSeenNotifications.length > 0 // we have to give Dropdown component some time // before removing notification item node from the list // otherwise dropdown thinks we clicked outside and closes dropdown @@ -70,7 +73,7 @@ const NotificationsDropdownContainerView = (props) => { }, 0) } } - const hasNew = hasUnread && lastVisited < _.maxBy(_.map(notifications, n => new Date(n.date))) + let notificationsEmpty = (

@@ -92,6 +95,12 @@ const NotificationsDropdownContainerView = (props) => { ) } + const markNotificationsSeen = (isOpen) => { + if (isOpen) { + markAllNotificationsSeen(null, notifications) + } + } + // this function checks that notification is not seen yet, // before marking it as seen const markNotificationSeen = (notificationId) => { @@ -118,10 +127,10 @@ const NotificationsDropdownContainerView = (props) => { return ( { toggleNotificationsDropdownWeb(isOpen) - visitNotifications() + markNotificationsSeen(isOpen) }} > {isDropdownWebOpen &&

@@ -192,10 +201,9 @@ const NotificationsDropdownContainerView = (props) => { return ( { toggleNotificationsDropdownMobile() - visitNotifications() }} isOpen={isDropdownMobileOpen} > @@ -253,7 +261,6 @@ class NotificationsDropdownContainer extends React.Component { constructor(props) { super(props) this.state = { - lastVisited: new Date(0), isDropdownWebOpen: false, isDropdownMobileOpen: false, notificationsVisited: false, @@ -261,13 +268,11 @@ class NotificationsDropdownContainer extends React.Component { this.onToggleNotificationsDropdownWeb = this.onToggleNotificationsDropdownWeb.bind(this) this.onToggleNotificationsDropdownMobile = this.onToggleNotificationsDropdownMobile.bind(this) - this.onVisitNotifications = this.onVisitNotifications.bind(this) } componentDidMount() { this.props.getNotifications() this.autoRefreshNotifications = setInterval(() => this.props.getNotifications(), REFRESH_NOTIFICATIONS_INTERVAL) - this.setState({ lastVisited: this.props.lastVisited }) } componentWillUnmount() { @@ -276,7 +281,6 @@ class NotificationsDropdownContainer extends React.Component { this.onToggleNotificationsDropdownMobile(false) this.onToggleNotificationsDropdownWeb(false) this.props.hideOlderNotifications() - this.state.notificationsVisited && this.props.visitNotifications() } componentWillReceiveProps(nextProps) { @@ -300,25 +304,15 @@ class NotificationsDropdownContainer extends React.Component { this.setState({ isDropdownMobileOpen: !_.isUndefined(isOpen) ? isOpen : !this.state.isDropdownMobileOpen}) } - onVisitNotifications() { - this.setState({ - lastVisited: _.maxBy(_.map(this.props.notifications, n => new Date(n.date))), - notificationsVisited: true - }) - } - render() { const { notifications, ...restProps } = this.props const preRenderedNotifications = preRenderNotifications(notifications) - return ( @@ -331,9 +325,9 @@ const mapStateToProps = ({ notifications }) => notifications const mapDispatchToProps = { getNotifications, - visitNotifications, toggleNotificationSeen, markAllNotificationsRead, + markAllNotificationsSeen, toggleNotificationRead, toggleBundledNotificationRead, viewOlderNotifications, diff --git a/src/config/constants.js b/src/config/constants.js index c66464225..af2c28ecd 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -15,11 +15,11 @@ export const LOAD_ORG_CONFIG_FAILURE = 'LOAD_ORG_CONFIG_FAILURE' export const GET_NOTIFICATIONS_PENDING = 'GET_NOTIFICATIONS_PENDING' export const GET_NOTIFICATIONS_SUCCESS = 'GET_NOTIFICATIONS_SUCCESS' export const GET_NOTIFICATIONS_FAILURE = 'GET_NOTIFICATIONS_FAILURE' -export const VISIT_NOTIFICATIONS = 'VISIT_NOTIFICATIONS' export const SET_NOTIFICATIONS_FILTER_BY = 'SET_NOTIFICATIONS_FILTER_BY' export const MARK_ALL_NOTIFICATIONS_READ = 'MARK_ALL_NOTIFICATIONS_READ' export const TOGGLE_NOTIFICATION_READ = 'TOGGLE_NOTIFICATION_READ' export const TOGGLE_NOTIFICATION_SEEN = 'TOGGLE_NOTIFICATION_SEEN' +export const MARK_ALL_NOTIFICATIONS_SEEN = 'MARK_ALL_NOTIFICATIONS_SEEN' export const VIEW_OLDER_NOTIFICATIONS_SUCCESS = 'VIEW_OLDER_NOTIFICATIONS_SUCCESS' export const HIDE_OLDER_NOTIFICATIONS_SUCCESS = 'HIDE_OLDER_NOTIFICATIONS_SUCCESS' export const NOTIFICATIONS_PENDING = 'NOTIFICATIONS_PENDING' diff --git a/src/projects/detail/ProjectDetail.jsx b/src/projects/detail/ProjectDetail.jsx index 5bc2c3769..cf41a607f 100644 --- a/src/projects/detail/ProjectDetail.jsx +++ b/src/projects/detail/ProjectDetail.jsx @@ -8,6 +8,7 @@ import { renderComponent, branch, compose, withProps } from 'recompose' import { loadProjectDashboard } from '../actions/projectDashboard' import { clearLoadedProject } from '../actions/project' import { acceptOrRefuseInvite } from '../actions/projectMember' +import { loadProjects } from '../actions/loadProjects' import { LOAD_PROJECT_FAILURE, PROJECT_ROLE_CUSTOMER, PROJECT_ROLE_OWNER, @@ -163,6 +164,8 @@ class ProjectDetail extends Component { status: isJoining ? PROJECT_MEMBER_INVITE_STATUS_ACCEPTED : PROJECT_MEMBER_INVITE_STATUS_REFUSED }).then(() => { if(!isJoining) { + // navigate to project listing and reload projects + this.props.loadProjects({ sort: 'updatedAt desc' }) this.props.history.push('/projects/') } else { this.props.loadProjectDashboard(this.props.match.params.projectId) @@ -229,7 +232,7 @@ const mapStateToProps = ({projectState, projectDashboard, loadUser, productsTime } } -const mapDispatchToProps = { loadProjectDashboard, clearLoadedProject, acceptOrRefuseInvite } +const mapDispatchToProps = { loadProjectDashboard, clearLoadedProject, acceptOrRefuseInvite, loadProjects } ProjectDetail.propTypes = { project: PropTypes.object, diff --git a/src/projects/detail/components/Accordion/Accordion.jsx b/src/projects/detail/components/Accordion/Accordion.jsx index 4c3b8f500..cc1cbd971 100644 --- a/src/projects/detail/components/Accordion/Accordion.jsx +++ b/src/projects/detail/components/Accordion/Accordion.jsx @@ -11,6 +11,9 @@ import cn from 'classnames' import IconX from '../../../../assets/icons/ui-x-mark.svg' import IconCarretDown from '../../../../assets/icons/arrow-6px-carret-down-normal.svg' +import Tooltip from 'appirio-tech-react-components/components/Tooltip/Tooltip' +import { TOOLTIP_DEFAULT_DELAY } from '../../../../config/constants' + import './Accordion.scss' /** @@ -132,7 +135,14 @@ class Accordion extends React.Component { return (