Skip to content

Commit 0cf6f1d

Browse files
authored
Merge pull request #3430 from appirio-tech/feature/member-list-redesign
[V4] Member List UI Redesign
2 parents 3acd97e + 07860a8 commit 0cf6f1d

File tree

19 files changed

+477
-60
lines changed

19 files changed

+477
-60
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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,23 @@ export function removeProjectMember(projectId, memberId) {
6666
return memberId
6767
})
6868
}
69+
70+
export function getProjectMembers(projectId) {
71+
const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,firstName,lastName,photoURL,workingHourStart,workingHourEnd,timeZone'
72+
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/?fields=`
73+
+ encodeURIComponent(fields)
74+
return axios.get(url)
75+
.then( resp => {
76+
return resp.data.result.content
77+
})
78+
}
79+
80+
export function getProjectMember(projectId, memberId) {
81+
const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,firstName,lastName,photoURL,workingHourStart,workingHourEnd,timeZone'
82+
const url = `${PROJECTS_API_URL}/v4/projects/${projectId}/members/${memberId}?fields=`
83+
+ encodeURIComponent(fields)
84+
return axios.get(url)
85+
.then( resp => {
86+
return resp.data.result.content
87+
})
88+
}

src/assets/icons/daylight.svg

Lines changed: 13 additions & 0 deletions
Loading

src/assets/icons/moon.svg

Lines changed: 13 additions & 0 deletions
Loading
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+
}

src/components/TeamManagement/ProjectManagementDialog.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class ProjectManagementDialog extends React.Component {
135135
<div className="memer-details">
136136
<Avatar
137137
userName={userFullName}
138-
avatarUrl={getAvatarResized(_.get(member, 'photoURL'), 40)}
138+
avatarUrl={getAvatarResized(_.get(member, 'photoURL') || '', 40)}
139139
size={40}
140140
/>
141141
<div className="member-name">
@@ -156,23 +156,24 @@ class ProjectManagementDialog extends React.Component {
156156
removeInvite(invite)
157157
}
158158
i++
159-
const handle = invite.member ? invite.member.handle : null
160-
const userFullName = getFullNameWithFallback(invite.member)
159+
const hasUserId = !_.isNil(invite.userId)
160+
const handle = invite.handle
161+
const userFullName = getFullNameWithFallback(invite)
161162
return (
162163
<div
163164
key={i}
164165
className={`project-member-layout ${(i % 2 !== 0) ? 'dark' : ''}`}
165166
>
166167
<Avatar
167-
userName={invite.email || userFullName}
168-
avatarUrl={invite.email ? '' : getAvatarResized(_.get(invite.member || {}, 'photoURL'), 40)}
168+
userName={hasUserId ? userFullName : invite.email}
169+
avatarUrl={hasUserId ? getAvatarResized(_.get(invite, 'photoURL') || '', 40) : ''}
169170
size={40}
170171
/>
171172
<div className="member-name">
172-
{!invite.email && <span className="span-name">{userFullName}</span>}
173+
{hasUserId && <span className="span-name">{userFullName}</span>}
173174
<span className="member-handle-container">
174-
{!invite.email && <span className="member-handle">@{handle}</span>}
175-
{invite.email && <span className="member-email">{invite.email}</span>}
175+
{hasUserId && handle && <span className="member-handle">@{handle}</span>}
176+
{ (!hasUserId) && <span className="member-email">{invite.email}</span>}
176177
</span>
177178
</div>
178179
{showRemove && <div className="member-remove" onClick={remove}>

0 commit comments

Comments
 (0)