Feature/users and groups UI updated as per new design (#4134)

This commit is contained in:
Shubham Thakre 2022-02-17 23:57:07 +05:30 committed by GitHub
parent 585aad1aac
commit 413990d3e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 789 additions and 94 deletions

View File

@ -0,0 +1,22 @@
import React from 'react';
import styled from 'styled-components';
import { EmbeddedListSearch } from '../shared/components/styled/search/EmbeddedListSearch';
import { useEntityData } from '../shared/EntityContext';
const UserAssetsWrapper = styled.div`
height: calc(100vh - 114px);
overflow: auto;
`;
export const UserAssets = () => {
const { urn } = useEntityData();
return (
<UserAssetsWrapper>
<EmbeddedListSearch
fixedFilter={{ field: 'owners', value: urn }}
emptySearchQuery="*"
placeholderText="Filter domain entities..."
/>
</UserAssetsWrapper>
);
};

View File

@ -0,0 +1,224 @@
import React, { useState } from 'react';
import { message, Button, Input, Modal, Typography, Form } from 'antd';
import { useUpdateCorpUserPropertiesMutation } from '../../../graphql/user.generated';
type PropsData = {
name: string | undefined;
title: string | undefined;
image: string | undefined;
team: string | undefined;
email: string | undefined;
slack: string | undefined;
phone: string | undefined;
urn: string | undefined;
};
type Props = {
visible: boolean;
onClose: () => void;
onSave: () => void;
editModalData: PropsData;
};
/** Regex Validations */
export const USER_NAME_REGEX = new RegExp('^[a-zA-Z ]*$');
export default function UserEditProfileModal({ visible, onClose, onSave, editModalData }: Props) {
const [updateCorpUserPropertiesMutation] = useUpdateCorpUserPropertiesMutation();
const [form] = Form.useForm();
const [saveButtonEnabled, setSaveButtonEnabled] = useState(true);
const [data, setData] = useState<PropsData>({
name: editModalData.name,
title: editModalData.title,
image: editModalData.image,
team: editModalData.team,
email: editModalData.email,
slack: editModalData.slack,
phone: editModalData.phone,
urn: editModalData.urn,
});
// save changes function
const onSaveChanges = () => {
updateCorpUserPropertiesMutation({
variables: {
urn: editModalData?.urn || '',
input: {
displayName: data.name,
title: data.title,
pictureLink: data.image,
teams: data.team?.split(','),
email: data.email,
slack: data.slack,
phone: data.phone,
},
},
})
.catch((e) => {
message.destroy();
message.error({ content: `Failed to Save changes!: \n ${e.message || ''}`, duration: 3 });
})
.finally(() => {
message.success({
content: `Changes saved.`,
duration: 3,
});
onSave(); // call the refetch function once save
// clear the values from edit profile form
setData({
name: '',
title: '',
image: '',
team: '',
email: '',
slack: '',
phone: '',
urn: '',
});
});
onClose();
};
return (
<Modal
title="Edit Profile"
visible={visible}
onCancel={onClose}
footer={
<>
<Button onClick={onClose} type="text">
Cancel
</Button>
<Button onClick={onSaveChanges} disabled={saveButtonEnabled}>
Save Changes
</Button>
</>
}
>
<Form
form={form}
initialValues={{ ...editModalData }}
autoComplete="off"
layout="vertical"
onFieldsChange={() =>
setSaveButtonEnabled(form.getFieldsError().some((field) => field.errors.length > 0))
}
>
<Form.Item
name="name"
label={<Typography.Text strong>Name</Typography.Text>}
rules={[
{
required: true,
message: 'Enter a display name.',
},
{ whitespace: true },
{ min: 2, max: 50 },
{
pattern: USER_NAME_REGEX,
message: '',
},
]}
hasFeedback
>
<Input
placeholder="John Smith"
value={data.name}
onChange={(event) => setData({ ...data, name: event.target.value })}
/>
</Form.Item>
<Form.Item
name="title"
label={<Typography.Text strong>Title/Role</Typography.Text>}
rules={[{ whitespace: true }, { min: 2, max: 50 }]}
hasFeedback
>
<Input
placeholder="Data Analyst"
value={data.title}
onChange={(event) => setData({ ...data, title: event.target.value })}
/>
</Form.Item>
<Form.Item
name="image"
label={<Typography.Text strong>Image URL</Typography.Text>}
rules={[{ whitespace: true }, { type: 'url', message: 'not valid url' }]}
hasFeedback
>
<Input
placeholder="https://www.example.com/photo.png"
value={data.image}
onChange={(event) => setData({ ...data, image: event.target.value })}
/>
</Form.Item>
<Form.Item
name="team"
label={<Typography.Text strong>Team</Typography.Text>}
rules={[{ whitespace: true }, { min: 2, max: 50 }]}
>
<Input
placeholder="Product Engineering"
value={data.team}
onChange={(event) => setData({ ...data, team: event.target.value })}
/>
</Form.Item>
<Form.Item
name="email"
label={<Typography.Text strong>Email</Typography.Text>}
rules={[
{
required: true,
message: 'Enter your email',
},
{
type: 'email',
message: 'Please enter valid email',
},
{ whitespace: true },
{ min: 2, max: 50 },
]}
hasFeedback
>
<Input
placeholder="john.smith@example.com"
value={data.email}
onChange={(event) => setData({ ...data, email: event.target.value })}
/>
</Form.Item>
<Form.Item
name="slack"
label={<Typography.Text strong>Slack</Typography.Text>}
rules={[{ whitespace: true }, { min: 2, max: 50 }]}
hasFeedback
>
<Input
placeholder="john_smith"
value={data.slack}
onChange={(event) => setData({ ...data, slack: event.target.value })}
/>
</Form.Item>
<Form.Item
name="phone"
label={<Typography.Text strong>Phone</Typography.Text>}
rules={[
{
pattern: new RegExp('^(?=.*[0-9])[- +()0-9]+$'),
message: 'not valid phone number',
},
{
min: 5,
max: 15,
},
]}
hasFeedback
>
<Input
placeholder="444-999-9999"
value={data.phone}
onChange={(event) => setData({ ...data, phone: event.target.value })}
/>
</Form.Item>
</Form>
</Modal>
);
}

View File

@ -1,10 +1,10 @@
import { List, Pagination, Row, Space, Typography } from 'antd';
import { Col, Pagination, Row, Tooltip } from 'antd';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { useGetUserGroupsLazyQuery } from '../../../graphql/user.generated';
import { CorpGroup, EntityRelationshipsResult, EntityType } from '../../../types.generated';
import { useEntityRegistry } from '../../useEntityRegistry';
import { PreviewType } from '../Entity';
type Props = {
urn: string;
@ -12,30 +12,67 @@ type Props = {
pageSize: number;
};
const GroupList = styled(List)`
&&& {
const GroupsViewWrapper = styled.div`
height: calc(100vh - 173px);
overflow-y: auto;
.user-group-pagination {
justify-content: center;
bottom: 24px;
position: absolute;
width: 100%;
border-color: ${(props) => props.theme.styles['border-color-base']};
margin-top: 12px;
margin-bottom: 28px;
padding: 24px 32px;
box-shadow: ${(props) => props.theme.styles['box-shadow']};
}
& li {
padding-top: 28px;
padding-bottom: 28px;
}
& li:not(:last-child) {
border-bottom: 1.5px solid #ededed;
left: 50%;
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
-webkit-transform: translateX(-50%);
-ms-transform: translateX(-50%);
transform: translateX(-50%);
}
`;
const GroupsView = styled(Space)`
width: 100%;
margin-bottom: 32px;
padding-top: 28px;
const GroupItemColumn = styled(Col)`
padding: 10px;
`;
const GroupItem = styled.div`
border: 1px solid #d9d9d9;
padding: 10px;
min-height: 107px;
max-height: 107px;
.title-row {
padding: 9px 11px 9px 11px;
}
.description-row {
padding: 2px 13px;
}
`;
const GroupTitle = styled.span`
font-size: 14px;
line-height: 22px;
font-weight: bold;
color: #262626;
`;
const GroupMember = styled.span`
font-weight: 500;
font-size: 12px;
line-height: 23px;
color: #8c8c8c;
padding-left: 7px;
`;
const GroupDescription = styled.span`
font-weight: 500;
font-size: 12px;
line-height: 20px;
color: #262626;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
height: 43px;
`;
export default function UserGroups({ urn, initialRelationships, pageSize }: Props) {
const [page, setPage] = useState(1);
const entityRegistry = useEntityRegistry();
@ -53,19 +90,35 @@ export default function UserGroups({ urn, initialRelationships, pageSize }: Prop
const userGroups = relationships?.relationships?.map((rel) => rel.entity as CorpGroup) || [];
return (
<GroupsView direction="vertical" size="middle">
<Typography.Title level={3}>Group Membership</Typography.Title>
<Row justify="center">
<GroupList
dataSource={userGroups}
split={false}
renderItem={(item, _) => (
<List.Item>
{entityRegistry.renderPreview(EntityType.CorpGroup, PreviewType.PREVIEW, item)}
</List.Item>
)}
bordered
/>
<GroupsViewWrapper>
<Row justify="space-between">
{userGroups &&
userGroups.map((item) => {
return (
<GroupItemColumn xl={8} lg={8} md={12} sm={12} xs={24} key={item.urn}>
<Link to={entityRegistry.getEntityUrl(EntityType.CorpGroup, item.urn)}>
<GroupItem>
<Row className="title-row">
<GroupTitle>{item.info?.displayName || item.name}</GroupTitle>
<GroupMember>
{item.relationships?.total}
{item.relationships?.total === 1 ? ' member' : ' members'}
</GroupMember>
</Row>
<Row className="description-row">
<GroupDescription>
<Tooltip title={item.info?.description}>
{item.info?.description}
</Tooltip>
</GroupDescription>
</Row>
</GroupItem>
</Link>
</GroupItemColumn>
);
})}
</Row>
<Row className="user-group-pagination">
<Pagination
current={page}
pageSize={pageSize}
@ -75,6 +128,6 @@ export default function UserGroups({ urn, initialRelationships, pageSize }: Prop
showSizeChanger={false}
/>
</Row>
</GroupsView>
</GroupsViewWrapper>
);
}

View File

@ -0,0 +1,337 @@
import { Divider, message, Space, Button, Tag, Typography } from 'antd';
import React, { useState } from 'react';
import styled from 'styled-components';
import { EditOutlined, MailOutlined, PhoneOutlined, SlackOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import { useUpdateCorpUserPropertiesMutation } from '../../../graphql/user.generated';
import { EntityType } from '../../../types.generated';
import UserEditProfileModal from './UserEditProfileModal';
import { ExtendedEntityRelationshipsResult } from './type';
import CustomAvatar from '../../shared/avatar/CustomAvatar';
import { useEntityRegistry } from '../../useEntityRegistry';
import { useGetAuthenticatedUser } from '../../useGetAuthenticatedUser';
const { Paragraph } = Typography;
type SideBarData = {
photoUrl: string | undefined;
avatarName: string | undefined;
name: string | undefined;
role: string | undefined;
team: string | undefined;
email: string | undefined;
slack: string | undefined;
phone: string | undefined;
aboutText: string | undefined;
groupsDetails: ExtendedEntityRelationshipsResult;
urn: string | undefined;
};
type Props = {
sideBarData: SideBarData;
refetch: () => void;
};
const AVATAR_STYLE = { marginTop: '14px' };
/**
* Styled Components
*/
export const SideBar = styled.div`
padding: 0 0 0 17px;
text-align: center;
font-style: normal;
font-weight: bold;
height: calc(100vh - 60px);
position: relative;
&&& .ant-avatar.ant-avatar-icon {
font-size: 46px !important;
}
.divider-infoSection {
margin: 18px 0px 18px 0;
}
.divider-aboutSection {
margin: 23px 0px 11px 0;
}
.divider-groupsSection {
margin: 23px 0px 11px 0;
}
`;
export const SideBarSubSection = styled.div`
height: calc(100vh - 135px);
overflow: auto;
padding-right: 18px;
&.fullView {
height: calc(100vh - 70px);
}
&::-webkit-scrollbar {
height: 12px;
width: 1px;
background: #f1f1f1;
}
&::-webkit-scrollbar-thumb {
background: #c3c3c3;
-webkit-border-radius: 1ex;
-webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.75);
}
`;
export const EmptyValue = styled.div`
&:after {
content: 'None';
color: #b7b7b7;
font-style: italic;
font-weight: 100;
}
`;
export const Name = styled.div`
font-size: 20px;
line-height: 28px;
color: #262626;
margin: 13px 0 7px 0;
`;
export const Role = styled.div`
font-size: 14px;
line-height: 22px;
color: #595959;
margin-bottom: 7px;
`;
export const Team = styled.div`
font-size: 12px;
line-height: 20px;
color: #8c8c8c;
`;
export const SocialDetails = styled.div`
font-size: 12px;
line-height: 20px;
color: #262626;
text-align: left;
margin: 6px 0;
`;
export const EditProfileButton = styled.div`
bottom: 24px;
position: absolute;
right: 27px;
width: 80%;
left: 50%;
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
transform: translateX(-50%);
button {
width: 100%;
font-size: 12px;
line-height: 20px;
color: #262626;
}
`;
export const AboutSection = styled.div`
text-align: left;
font-weight: bold;
font-size: 14px;
line-height: 22px;
color: #262626;
`;
export const AboutSectionText = styled.div`
font-size: 12px;
font-weight: 100;
line-height: 15px;
padding: 5px 0;
&&& .ant-typography {
margin-bottom: 0;
}
&&& .ant-typography-edit-content {
padding-left: 15px;
padding-top: 5px;
}
`;
export const GroupsSection = styled.div`
text-align: left;
font-weight: bold;
font-size: 14px;
line-height: 22px;
color: #262626;
`;
export const TagsSection = styled.div`
height: calc(75vh - 460px);
padding: 5px;
`;
export const NoDataFound = styled.span`
font-size: 12px;
color: #262626;
font-weight: 100;
`;
export const Tags = styled.div`
margin-top: 5px;
`;
export const GroupsSeeMoreText = styled.span`
font-weight: 500;
font-size: 12px;
line-height: 20px;
color: #1890ff;
cursor: pointer;
`;
/**
* Responsible for reading & writing users.
*/
export default function UserInfoSideBar({ sideBarData, refetch }: Props) {
const { name, aboutText, avatarName, email, groupsDetails, phone, photoUrl, role, slack, team, urn } = sideBarData;
const [updateCorpUserPropertiesMutation] = useUpdateCorpUserPropertiesMutation();
const entityRegistry = useEntityRegistry();
const [groupSectionExpanded, setGroupSectionExpanded] = useState(false);
const [editProfileModal, showEditProfileModal] = useState(false);
/* eslint-disable @typescript-eslint/no-unused-vars */
const [editableAboutMeText, setEditableAboutMeText] = useState<string | undefined>('');
const me = useGetAuthenticatedUser();
const showEditProfileButton = me?.corpUser?.urn === urn;
const getEditModalData = {
urn,
name,
title: role,
team,
email,
image: photoUrl,
slack,
phone,
};
// About Text save
const onSaveAboutMe = (inputString) => {
setEditableAboutMeText(inputString);
updateCorpUserPropertiesMutation({
variables: {
urn: urn || '',
input: {
aboutMe: inputString,
},
},
})
.catch((e) => {
message.destroy();
message.error({ content: `Failed to Save changes!: \n ${e.message || ''}`, duration: 3 });
})
.finally(() => {
message.success({
content: `Changes saved.`,
duration: 3,
});
refetch();
});
};
return (
<>
<SideBar>
<SideBarSubSection className={showEditProfileButton ? '' : 'fullView'}>
<CustomAvatar size={160} photoUrl={photoUrl} name={avatarName} style={AVATAR_STYLE} />
<Name>{name || <EmptyValue />}</Name>
<Role>{role || <EmptyValue />}</Role>
<Team>{team || <EmptyValue />}</Team>
<Divider className="divider-infoSection" />
<SocialDetails>
<Space>
<MailOutlined />
{email || <EmptyValue />}
</Space>
</SocialDetails>
<SocialDetails>
<Space>
<SlackOutlined />
{slack || <EmptyValue />}
</Space>
</SocialDetails>
<SocialDetails>
<Space>
<PhoneOutlined />
{phone || <EmptyValue />}
</Space>
</SocialDetails>
<Divider className="divider-aboutSection" />
<AboutSection>
About
<AboutSectionText>
<Paragraph
editable={{ onChange: onSaveAboutMe }}
ellipsis={{ rows: 2, expandable: true, symbol: 'Read more' }}
>
{aboutText || <EmptyValue />}
</Paragraph>
</AboutSectionText>
</AboutSection>
<Divider className="divider-groupsSection" />
<GroupsSection>
Groups
<TagsSection>
{groupsDetails?.relationships.length === 0 && <NoDataFound>No Groups</NoDataFound>}
{!groupSectionExpanded &&
groupsDetails?.relationships.slice(0, 2).map((item) => {
return (
<Link to={entityRegistry.getEntityUrl(EntityType.CorpGroup, item.entity.urn)}>
<Tags>
<Tag>
{item.entity.info.displayName || item.entity.name || <EmptyValue />}
</Tag>
</Tags>
</Link>
);
})}
{groupSectionExpanded &&
groupsDetails?.relationships.length > 2 &&
groupsDetails?.relationships.map((item) => {
return (
<Tags>
<Tag>
{item.entity.info.displayName || item.entity.name || <EmptyValue />}
</Tag>
</Tags>
);
})}
{!groupSectionExpanded && groupsDetails?.relationships.length > 2 && (
<GroupsSeeMoreText onClick={() => setGroupSectionExpanded(!groupSectionExpanded)}>
{`+${groupsDetails?.relationships.length - 2} more`}
</GroupsSeeMoreText>
)}
</TagsSection>
</GroupsSection>
</SideBarSubSection>
{showEditProfileButton && (
<EditProfileButton>
<Button icon={<EditOutlined />} onClick={() => showEditProfileModal(true)}>
Edit Profile
</Button>
</EditProfileButton>
)}
</SideBar>
{/* Modal */}
<UserEditProfileModal
visible={editProfileModal}
onClose={() => showEditProfileModal(false)}
onSave={() => {
refetch();
}}
editModalData={getEditModalData}
/>
</>
);
}

View File

@ -1,26 +1,57 @@
import { Alert } from 'antd';
import React, { useMemo } from 'react';
import UserHeader from './UserHeader';
import { Alert, Col, Row } from 'antd';
import React from 'react';
import styled from 'styled-components';
import useUserParams from '../../shared/entitySearch/routingUtils/useUserParams';
import { useGetUserQuery } from '../../../graphql/user.generated';
import { useGetAllEntitySearchResults } from '../../../utils/customGraphQL/useGetAllEntitySearchResults';
import { Message } from '../../shared/Message';
import RelatedEntityResults from '../../shared/entitySearch/RelatedEntityResults';
import { LegacyEntityProfile } from '../../shared/LegacyEntityProfile';
import { CorpUser, EntityType, SearchResult, EntityRelationshipsResult } from '../../../types.generated';
import { EntityRelationshipsResult } from '../../../types.generated';
import UserGroups from './UserGroups';
import { useEntityRegistry } from '../../useEntityRegistry';
import { RoutedTabs } from '../../shared/RoutedTabs';
import { UserAssets } from './UserAssets';
import { ExtendedEntityRelationshipsResult } from './type';
import { decodeUrn } from '../shared/utils';
import UserInfoSideBar from './UserInfoSideBar';
const messageStyle = { marginTop: '10%' };
export interface Props {
onTabChange: (selectedTab: string) => void;
}
export enum TabType {
Ownership = 'Ownership',
Assets = 'Assets',
Groups = 'Groups',
}
const ENABLED_TAB_TYPES = [TabType.Ownership, TabType.Groups];
const ENABLED_TAB_TYPES = [TabType.Assets, TabType.Groups];
const GROUP_PAGE_SIZE = 20;
const MESSAGE_STYLE = { marginTop: '10%' };
/**
* Styled Components
*/
const UserProfileWrapper = styled.div`
&&& .ant-tabs-nav {
margin: 0;
}
`;
const Content = styled.div`
color: #262626;
height: calc(100vh - 60px);
&&& .ant-tabs > .ant-tabs-nav .ant-tabs-nav-wrap {
padding-left: 15px;
}
`;
export const EmptyValue = styled.div`
&:after {
content: 'None';
color: #b7b7b7;
font-style: italic;
font-weight: 100;
}
`;
/**
* Responsible for reading & writing users.
@ -28,46 +59,36 @@ const GROUP_PAGE_SIZE = 20;
export default function UserProfile() {
const { urn: encodedUrn } = useUserParams();
const urn = decodeUrn(encodedUrn);
const { loading, error, data } = useGetUserQuery({ variables: { urn, groupsCount: GROUP_PAGE_SIZE } });
const entityRegistry = useEntityRegistry();
const username = data?.corpUser?.username;
const { loading, error, data, refetch } = useGetUserQuery({ variables: { urn, groupsCount: GROUP_PAGE_SIZE } });
const username = data?.corpUser?.username;
const ownershipResult = useGetAllEntitySearchResults({
query: `owners:${username}`,
});
const groupMemberRelationships = data?.corpUser?.relationships as EntityRelationshipsResult;
const groupsDetails = data?.corpUser?.relationships as ExtendedEntityRelationshipsResult;
const contentLoading =
Object.keys(ownershipResult).some((type) => {
return ownershipResult[type].loading;
}) || loading;
const ownershipForDetails = useMemo(() => {
const filteredOwnershipResult: {
[key in EntityType]?: Array<SearchResult>;
} = {};
Object.keys(ownershipResult).forEach((type) => {
const entities = ownershipResult[type].data?.search?.searchResults;
if (entities && entities.length > 0) {
filteredOwnershipResult[type] = ownershipResult[type].data?.search?.searchResults;
}
});
return filteredOwnershipResult;
}, [ownershipResult]);
if (error || (!loading && !error && !data)) {
return <Alert type="error" message={error?.message || 'Entity failed to load'} />;
}
const groupMemberRelationships = data?.corpUser?.relationships as EntityRelationshipsResult;
// Routed Tabs Constants
const getTabs = () => {
return [
{
name: TabType.Ownership,
path: TabType.Ownership.toLocaleLowerCase(),
content: <RelatedEntityResults searchResult={ownershipForDetails} />,
name: TabType.Assets,
path: TabType.Assets.toLocaleLowerCase(),
content: <UserAssets />,
display: {
enabled: () => true,
},
},
{
name: TabType.Groups,
@ -75,36 +96,48 @@ export default function UserProfile() {
content: (
<UserGroups urn={urn} initialRelationships={groupMemberRelationships} pageSize={GROUP_PAGE_SIZE} />
),
display: {
enabled: () => groupsDetails?.relationships.length > 0,
},
},
].filter((tab) => ENABLED_TAB_TYPES.includes(tab.name));
};
const defaultTabPath = getTabs() && getTabs()?.length > 0 ? getTabs()[0].path : '';
const onTabChange = () => null;
const getHeader = (user: CorpUser) => {
const { editableInfo, info } = user;
const displayName = entityRegistry.getDisplayName(EntityType.CorpUser, user);
return (
<UserHeader
profileSrc={editableInfo?.pictureLink}
name={displayName}
title={info?.title}
email={info?.email}
skills={editableInfo?.skills}
teams={editableInfo?.teams}
/>
);
// Side bar data
const sideBarData = {
photoUrl: data?.corpUser?.editableProperties?.pictureLink || undefined,
avatarName:
data?.corpUser?.editableProperties?.displayName ||
data?.corpUser?.info?.displayName ||
data?.corpUser?.info?.fullName ||
data?.corpUser?.urn,
name: data?.corpUser?.editableProperties?.displayName || data?.corpUser?.info?.fullName || undefined,
role: data?.corpUser?.editableProperties?.title || data?.corpUser?.info?.title || undefined,
team: data?.corpUser?.editableProperties?.teams?.join(',') || undefined,
email: data?.corpUser?.editableProperties?.email || data?.corpUser?.info?.email || undefined,
slack: data?.corpUser?.editableProperties?.slack || undefined,
phone: data?.corpUser?.editableProperties?.phone || undefined,
aboutText: data?.corpUser?.editableProperties?.aboutMe || undefined,
groupsDetails: data?.corpUser?.relationships as ExtendedEntityRelationshipsResult,
urn,
};
return (
<>
{contentLoading && <Message type="loading" content="Loading..." style={messageStyle} />}
{data && data.corpUser && (
<LegacyEntityProfile
title=""
tags={null}
header={getHeader(data.corpUser as CorpUser)}
tabs={getTabs()}
/>
)}
{contentLoading && <Message type="loading" content="Loading..." style={MESSAGE_STYLE} />}
<UserProfileWrapper>
<Row>
<Col xl={5} lg={5} md={5} sm={24} xs={24}>
<UserInfoSideBar sideBarData={sideBarData} refetch={refetch} />
</Col>
<Col xl={19} lg={19} md={19} sm={24} xs={24} style={{ borderLeft: '1px solid #E9E9E9' }}>
<Content>
<RoutedTabs defaultPath={defaultTabPath} tabs={getTabs()} onTabChange={onTabChange} />
</Content>
</Col>
</Row>
</UserProfileWrapper>
</>
);
}

View File

@ -0,0 +1,14 @@
import { EntityRelationshipsResult, EntityRelationship, Entity, CorpGroupProperties } from '../../../types.generated';
export interface ExtendedEntityRelationshipsResult extends EntityRelationshipsResult {
relationships: Array<ExtendedEntityRelationship>;
}
interface ExtendedEntityRelationship extends EntityRelationship {
entity: ExtendedEntity;
}
interface ExtendedEntity extends Entity {
info: CorpGroupProperties;
name: string;
}

View File

@ -12,6 +12,9 @@ interface Props extends TabsProps {
name: string;
path: string;
content: React.ReactNode;
display?: {
enabled: () => boolean;
};
}>;
onTabChange?: (selectedTab: string) => void;
}
@ -39,9 +42,11 @@ export const RoutedTabs = ({ defaultPath, tabs, onTabChange, ...props }: Props)
onChange={(newPath) => history.push(`${url}/${newPath}`)}
{...props}
>
{tabs.map((tab) => (
<TabPane tab={tab.name} key={tab.path.replace('/', '')} />
))}
{tabs.map((tab) => {
return (
<TabPane tab={tab.name} key={tab.path.replace('/', '')} disabled={!tab.display?.enabled()} />
);
})}
</Tabs>
<Switch>
<Route exact path={path}>

View File

@ -10,11 +10,18 @@ query getUser($urn: String!, $groupsCount: Int!) {
lastName
fullName
email
departmentName
}
editableInfo {
editableProperties {
slack
phone
pictureLink
aboutMe
teams
skills
displayName
title
email
}
globalTags {
...globalTagsFields