Skip to content

Commit b31dff1

Browse files
author
vikasrohit
authored
Merge pull request #3439 from appirio-tech/dev
Production release 2.4.16
2 parents 0561d92 + 5710ff4 commit b31dff1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+852
-171
lines changed

src/api/projectMemberInvites.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export function updateProjectMemberInvite(projectId, member) {
2222
* @return {object} project member invite returned by api
2323
*/
2424
export function createProjectMemberInvite(projectId, member) {
25-
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/invite/`
25+
const fields = 'id,projectId,userId,email,role,status,createdAt,updatedAt,createdBy,updatedBy,handle,firstName,lastName,photoURL'
26+
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/invite/?fields=` + encodeURIComponent(fields)
2627
return axios({
2728
method: 'post',
2829
url,
@@ -38,6 +39,16 @@ export function createProjectMemberInvite(projectId, member) {
3839
})
3940
}
4041

42+
export function getProjectMemberInvites(projectId) {
43+
const fields = 'id,projectId,userId,email,role,status,createdAt,updatedAt,createdBy,updatedBy,handle,firstName,lastName,photoURL'
44+
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/invites/?fields=`
45+
+ encodeURIComponent(fields)
46+
return axios.get(url)
47+
.then( resp => {
48+
return resp.data.result.content
49+
})
50+
}
51+
4152
/**
4253
* Get a project member invite based on project's id
4354
* @param {integer} projectId unique identifier of the project

src/api/projectMembers.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ export function addProjectMember(projectId, newMember) {
5151

5252

5353
export function updateProjectMember(projectId, memberId, updatedProps) {
54-
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/${memberId}/`
54+
const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,firstName,lastName,photoURL,workingHourStart,workingHourEnd,timeZone'
55+
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/${memberId}/?fields=`
56+
+ encodeURIComponent(fields)
5557
return axios.patch(url, { param: updatedProps })
5658
.then(resp => {
5759
return resp.data.result.content
@@ -66,3 +68,23 @@ export function removeProjectMember(projectId, memberId) {
6668
return memberId
6769
})
6870
}
71+
72+
export function getProjectMembers(projectId) {
73+
const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,firstName,lastName,photoURL,workingHourStart,workingHourEnd,timeZone'
74+
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/?fields=`
75+
+ encodeURIComponent(fields)
76+
return axios.get(url)
77+
.then( resp => {
78+
return resp.data.result.content
79+
})
80+
}
81+
82+
export function getProjectMember(projectId, memberId) {
83+
const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,firstName,lastName,photoURL,workingHourStart,workingHourEnd,timeZone'
84+
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/${memberId}?fields=`
85+
+ encodeURIComponent(fields)
86+
return axios.get(url)
87+
.then( resp => {
88+
return resp.data.result.content
89+
})
90+
}

src/api/templates.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,19 @@ export function updateProjectsMetadata(metadataId, type, data) {
9393
return axios.patch(`${PROJECTS_API_URL}/v4/projects/metadata/${type}/${key}/versions/${version}`, {
9494
param: tmpdata
9595
})
96-
.then(resp => _.get(resp.data, 'result.content', {}))
96+
.then(resp => {
97+
const versionMetadata = _.get(resp.data, 'result.content', {})
98+
return { type, versionMetadata }
99+
})
97100
} else {
98101
const path = type !== 'milestoneTemplates' ? 'projects' : 'timelines'
99102
return axios.patch(`${PROJECTS_API_URL}/v4/${path}/metadata/${type}/${metadataId}`, {
100103
param: data
101104
})
102-
.then(resp => _.get(resp.data, 'result.content', {}))
105+
.then(resp => {
106+
const metadata = _.get(resp.data, 'result.content', {})
107+
return { type, metadata }
108+
})
103109
}
104110
}
105111

src/assets/icons/daylight.svg

Lines changed: 13 additions & 0 deletions
Loading

src/assets/icons/moon.svg

Lines changed: 13 additions & 0 deletions
Loading

src/components/AuthenticatedComponent.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export function requiresAuthentication(Component) {
2222
this.setState({isLoggedIn: true})
2323
}).catch((error) => {
2424
console.log(error)
25-
// FIXME should we include hash, search etc
26-
const redirectBackToUrl = window.location.origin + this.props.location.pathname
25+
// we have to to redirect to the same page, so we use the whole URL
26+
const redirectBackToUrl = encodeURIComponent(window.location.href)
2727
const newLocation = ACCOUNTS_APP_LOGIN_URL + '?retUrl=' + redirectBackToUrl
2828
console.log('redirecting... ', newLocation)
2929
window.location = newLocation

src/components/ProjectStatus/editableProjectStatus.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
PROJECT_STATUS_ACTIVE,
1111
PROJECT_STATUS_COMPLETED,
1212
PROJECT_STATUS_CANCELLED,
13-
TOOLTIP_DEFAULT_DELAY
13+
TOOLTIP_DEFAULT_DELAY,
14+
PROJECT_STATUS_DRAFT
1415
} from '../../config/constants'
1516
import CarretDownNormal9px from '../../assets/icons/arrow-9px-carret-down-normal.svg'
1617

@@ -63,7 +64,7 @@ const hocStatusDropdown = (CompositeComponent, statusList, projectCanBeActive) =
6364
<div className="status-header">Project Status</div>
6465
<ul>
6566
{
66-
activestatusList.sort((a, b) => a.dropDownOrder > b.dropDownOrder).map((item) =>
67+
activestatusList.sort((a, b) => a.order - b.order).map((item) =>
6768
item.toolTipMessage ? (
6869
<Tooltip key={item.value} theme="light" tooltipDelay={TOOLTIP_DEFAULT_DELAY}>
6970
<div className="tooltip-target">
@@ -121,6 +122,7 @@ const editableProjectStatus = (CompositeComponent) => class extends Component {
121122
this.showStatusChangeDialog = this.showStatusChangeDialog.bind(this)
122123
this.changeStatus = this.changeStatus.bind(this)
123124
this.handleReasonUpdate = this.handleReasonUpdate.bind(this)
125+
this.getProjectStatusDropdownValues = this.getProjectStatusDropdownValues.bind(this)
124126
}
125127

126128
componentWillReceiveProps() {
@@ -158,12 +160,20 @@ const editableProjectStatus = (CompositeComponent) => class extends Component {
158160
this.setState({ statusChangeReason : _.get(reason, 'value') })
159161
}
160162

163+
getProjectStatusDropdownValues(status) {
164+
if (status === PROJECT_STATUS_DRAFT) {
165+
return [{color: 'gray', name: 'Draft', fullName: 'Project is in draft', value: PROJECT_STATUS_DRAFT, order: 2, dropDownOrder: 1 }].concat(PROJECT_STATUS)
166+
}
167+
return PROJECT_STATUS
168+
}
169+
161170
render() {
162171
const { showStatusChangeDialog, newStatus, statusChangeReason } = this.state
163-
const { canEdit, projectCanBeActive } = this.props
172+
const { canEdit, projectCanBeActive, status } = this.props
173+
const PROJECT_STATUS_VALUES = this.getProjectStatusDropdownValues(status)
164174
const StatusDropdown = canEdit
165-
? enhanceDropdown(hocStatusDropdown(CompositeComponent, PROJECT_STATUS, projectCanBeActive))
166-
: hocStatusDropdown(CompositeComponent, PROJECT_STATUS, projectCanBeActive)
175+
? enhanceDropdown(hocStatusDropdown(CompositeComponent, PROJECT_STATUS_VALUES, projectCanBeActive))
176+
: hocStatusDropdown(CompositeComponent, PROJECT_STATUS_VALUES, projectCanBeActive)
167177
return (
168178
<div className={cn('EditableProjectStatus', {'modal-active': showStatusChangeDialog})}>
169179
<div className="modal-overlay" onClick={ this.hideStatusChangeDialog }/>
@@ -190,7 +200,11 @@ editableProjectStatus.propTypes = {
190200
/**
191201
* Boolean flag to control if project status can be switched to active.
192202
*/
193-
projectCanBeActive: PropTypes.bool
203+
projectCanBeActive: PropTypes.bool,
204+
/**
205+
* String representing project status
206+
*/
207+
status: PropTypes.string
194208
}
195209

196210
export default editableProjectStatus

src/components/Sticky.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,26 @@ export default class Sticky extends React.Component {
77
super(props)
88
this.mountSticky = (sticky) => { this.sticky = sticky }
99
this.handleScroll = this.handleScroll.bind(this)
10+
this.updateSticky = this.updateSticky.bind(this)
1011
}
1112

1213
componentDidMount() {
1314
window.addEventListener('scroll', this.handleScroll)
15+
document.addEventListener('refreshsticky', this.updateSticky)
1416
}
1517

1618
componentWillUnmount() {
1719
window.removeEventListener('scroll', this.handleScroll)
20+
document.removeEventListener('refreshsticky', this.updateSticky)
21+
}
22+
23+
updateSticky() {
24+
setTimeout(() => {
25+
if (this.sticky) {
26+
this.sticky.updateInitialDimension()
27+
this.sticky.update()
28+
}
29+
})
1830
}
1931

2032
handleScroll() {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import moment from 'moment'
4+
import {timezones} from 'appirio-tech-react-components/constants/timezones'
5+
import UserTooltip from '../User/UserTooltip'
6+
import SunIcon from '../../assets/icons/daylight.svg'
7+
import MoonIcon from '../../assets/icons/moon.svg'
8+
import { getFullNameWithFallback } from '../../helpers/tcHelpers'
9+
import './MemberItem.scss'
10+
11+
const MemberItem = (props) => {
12+
13+
const {usr, showEmailOnly} = props
14+
15+
const userFullName = getFullNameWithFallback(usr)
16+
const workingHourStart = _.get(usr, 'workingHourStart')
17+
const workingHourEnd = _.get(usr, 'workingHourEnd')
18+
const timeZone = _.get(usr, 'timeZone')
19+
const email = _.get(usr, 'email')
20+
let localTime
21+
let timeZoneInfo
22+
if(timeZone) {
23+
timeZoneInfo = _.find(timezones, (t) => {return t.zoneName === timeZone})
24+
localTime = moment().utcOffset(timeZoneInfo.gmtOffset/3600).format('h:mm A Z')
25+
}
26+
let localTimeInfoEl = null
27+
28+
let isWorkingTime = false
29+
let showIcon = false
30+
let localWhStart
31+
let localWhEnd
32+
33+
if(localTime || workingHourStart && workingHourEnd ) {
34+
35+
if(workingHourStart && workingHourEnd) {
36+
showIcon = true
37+
localWhStart = moment({hour: workingHourStart.split(':')[0]}).format('h:mm A')
38+
localWhEnd = moment({hour: workingHourEnd.split(':')[0]}).format('h:mm A')
39+
40+
if(localTime) {
41+
let localHour = +moment().utcOffset(timeZoneInfo.gmtOffset/3600).format('H')
42+
const localStartHour = +moment({hour: workingHourStart.split(':')[0] }).format('H')
43+
let localEndHour = +moment({hour: workingHourEnd.split(':')[0] }).format('H')
44+
if(localEndHour <= localStartHour) {
45+
localEndHour += 24
46+
if(localHour < localStartHour) {
47+
localHour += 24
48+
}
49+
}
50+
if(localHour >= localStartHour && localHour <= localEndHour) {
51+
isWorkingTime = true
52+
}
53+
}
54+
}
55+
localTimeInfoEl = (<div styleName="time-info-tooltip">
56+
{localTime? <span>Local Time - {localTime}</span>: null}
57+
{localWhStart && localWhEnd ? <span>Working Hours - {`${localWhStart} - ${localWhEnd}`}</span>: null}
58+
</div>)
59+
}
60+
61+
return (
62+
<div styleName="container">
63+
<UserTooltip {...props} localTimeInfo={localTimeInfoEl}/>
64+
<div styleName="member-detail">
65+
<div styleName="member-name">{showEmailOnly? email :userFullName}</div>
66+
{localWhStart && localWhEnd && <div styleName="wk-hour">WH: {localWhStart} - {localWhEnd}</div>}
67+
{localTime &&<div styleName="local-time">{showIcon&& (isWorkingTime ? <SunIcon/>: <MoonIcon/>)}Local time: {localTime}</div>}
68+
</div>
69+
</div>
70+
)
71+
}
72+
73+
MemberItem.propTypes = {
74+
showRoleSelector: false
75+
}
76+
77+
MemberItem.propTypes = {
78+
usr: PropTypes.object.isRequired,
79+
id: PropTypes.oneOfType([
80+
PropTypes.string,
81+
PropTypes.number
82+
]).isRequired,
83+
previewAvatar: PropTypes.bool,
84+
showEmailOnly: PropTypes.bool,
85+
size: PropTypes.number
86+
}
87+
88+
export default MemberItem
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
@import '~tc-ui/src/styles/tc-includes';
2+
3+
.container {
4+
display: flex;
5+
flex-direction: row;
6+
min-height: 51px;
7+
width: 100%;
8+
9+
& + & {
10+
margin-top: 2 * $base-unit;
11+
}
12+
13+
:global(.Tooltip) {
14+
margin-left: 5px;
15+
margin-top: 2px;
16+
17+
&:global(.customer-data .tooltip-content-container){
18+
width: 440px;
19+
}
20+
}
21+
}
22+
23+
.member-detail {
24+
margin-left: 2 * $base-unit;
25+
overflow: hidden;
26+
}
27+
28+
.member-name {
29+
@include roboto-bold;
30+
font-size: 16px;
31+
line-height: 20px;
32+
overflow: hidden;
33+
text-overflow: ellipsis;
34+
white-space: nowrap;
35+
}
36+
37+
.wk-hour {
38+
margin-top: $base-unit;
39+
font-size: 10px;
40+
color: $tc-gray-50;
41+
}
42+
43+
.local-time {
44+
align-items: center;
45+
display: flex;
46+
font-size: 10px;
47+
color: $tc-gray-50;
48+
line-height: 20px;
49+
50+
svg {
51+
vertical-align: sub;
52+
margin-right: $base-unit;
53+
}
54+
}
55+
56+
.time-info-tooltip {
57+
height: 20px;
58+
width: 100%;
59+
color: $tc-gray-50;
60+
display: flex;
61+
justify-content: space-between;
62+
63+
span {
64+
font-size: 12px;
65+
color: $tc-gray-50;
66+
line-height: 20px;
67+
white-space: nowrap
68+
}
69+
}

0 commit comments

Comments
 (0)