Merge pull request #1819 from tristian2/tristian2-staff-directory
This commit is contained in:
commit
ad31c02a01
|
@ -31,7 +31,7 @@ This web part shows the current user's colleagues, and allows the user to search
|
||||||
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
||||||
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
|
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
|
||||||
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
||||||
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
![Teams Yes: Designed for Microsoft Teams](https://img.shields.io/badge/Teams-Yes-green.svg "Designed for Microsoft Teams")
|
||||||
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
||||||
|
|
||||||
## Applies to
|
## Applies to
|
||||||
|
@ -45,6 +45,8 @@ Solution|Author(s)
|
||||||
--------|---------
|
--------|---------
|
||||||
react-staffdirectory|Ari Gunawan ([@arigunawan3023](https://twitter.com/arigunawan3023))
|
react-staffdirectory|Ari Gunawan ([@arigunawan3023](https://twitter.com/arigunawan3023))
|
||||||
react-staffdirectory|João Mendes ([joaojmendes](https://github.com/joaojmendes))
|
react-staffdirectory|João Mendes ([joaojmendes](https://github.com/joaojmendes))
|
||||||
|
react-staffdirectory|[Tristian O'brien](https://github.com/tristian2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
@ -53,6 +55,7 @@ Version|Date|Comments
|
||||||
-------|----|--------
|
-------|----|--------
|
||||||
1.0.0|February 16, 2021|Initial release
|
1.0.0|February 16, 2021|Initial release
|
||||||
1.0.1|March 28, 2021|Added missing Graph API Permission (User.Read.All) for getting users information
|
1.0.1|March 28, 2021|Added missing Graph API Permission (User.Read.All) for getting users information
|
||||||
|
1.0.2|April 14, 2021|Added About Me and Skills
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"This web part shows the current user\u0027s colleagues, and allows the user to search AD directory, The user can configure the properties to show when expand the user card."
|
"This web part shows the current user\u0027s colleagues, and allows the user to search AD directory, The user can configure the properties to show when expand the user card."
|
||||||
],
|
],
|
||||||
"created": "2021-03-09",
|
"created": "2021-03-09",
|
||||||
"modified": "2021-03-28",
|
"modified": "2021-04-14",
|
||||||
"products": [
|
"products": [
|
||||||
"SharePoint",
|
"SharePoint",
|
||||||
"Office"
|
"Office"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"solution": {
|
"solution": {
|
||||||
"name": "staff-directory-client-side-solution",
|
"name": "staff-directory-client-side-solution",
|
||||||
"id": "89d7389c-be48-41e9-9f72-5eb9a1099c1f",
|
"id": "89d7389c-be48-41e9-9f72-5eb9a1099c1f",
|
||||||
"version": "1.0.1.0",
|
"version": "1.0.2.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"skipFeatureDeployment": true,
|
"skipFeatureDeployment": true,
|
||||||
"isDomainIsolated": false,
|
"isDomainIsolated": false,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "staff-directory",
|
"name": "staff-directory",
|
||||||
"version": "0.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
@ -19,7 +19,6 @@ import { IUserExtended } from "../../entites/IUserExtended";
|
||||||
import { IAppContext } from "../../common/IAppContext";
|
import { IAppContext } from "../../common/IAppContext";
|
||||||
import { IUserCardProps } from "./IUserCardProps";
|
import { IUserCardProps } from "./IUserCardProps";
|
||||||
|
|
||||||
|
|
||||||
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
|
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
|
||||||
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
|
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
|
||||||
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
|
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
|
||||||
|
@ -97,6 +96,7 @@ export const UserCard = (props: IUserCardProps) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//tris added onclick event
|
||||||
const _onRenderPrimaryText = (persona: IPersonaProps) => {
|
const _onRenderPrimaryText = (persona: IPersonaProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -108,7 +108,12 @@ export const UserCard = (props: IUserCardProps) => {
|
||||||
root: { justifyContent: "flex-start", width: "100%" },
|
root: { justifyContent: "flex-start", width: "100%" },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text onClick={
|
||||||
|
(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
window.open("https://gbr.delve.office.com/?u=" + userData.id + "&v=work", "_blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
variant="medium"
|
variant="medium"
|
||||||
block
|
block
|
||||||
nowrap
|
nowrap
|
||||||
|
@ -116,8 +121,9 @@ export const UserCard = (props: IUserCardProps) => {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
marginBottom: 3,
|
marginBottom: 3
|
||||||
}}
|
}}
|
||||||
|
title={persona.text}
|
||||||
>
|
>
|
||||||
{persona.text}
|
{persona.text}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -130,6 +136,7 @@ export const UserCard = (props: IUserCardProps) => {
|
||||||
>
|
>
|
||||||
<div style={{ fontSize: 12 }}>
|
<div style={{ fontSize: 12 }}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
height: 21,
|
height: 21,
|
||||||
|
@ -184,11 +191,19 @@ export const UserCard = (props: IUserCardProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//tris added onclick event
|
||||||
const _onRenderSecondaryText = (persona: IPersonaProps) => {
|
const _onRenderSecondaryText = (persona: IPersonaProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack verticalAlign="start" tokens={{ childrenGap: 0 }}>
|
<Stack verticalAlign="start" tokens={{ childrenGap: 0 }}>
|
||||||
<Text title={persona.secondaryText} variant="medium" block nowrap>
|
<Text onClick={
|
||||||
|
(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
window.open("https://gbr.delve.office.com/?u=" + userData.id + "&v=work", "_blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title={persona.secondaryText} variant="medium" block nowrap>
|
||||||
{" "}
|
{" "}
|
||||||
{persona.secondaryText}
|
{persona.secondaryText}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -708,7 +723,81 @@ export const UserCard = (props: IUserCardProps) => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
case "aboutMe":
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styleClasses.styleField}`}
|
||||||
|
title={userData.aboutMe ? userData.aboutMe.replace(/<[^>]+>/g, '').replace(/ /gi,' ') : "Not available"}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
verticalAlign="center"
|
||||||
|
tokens={{ childrenGap: 5 }}
|
||||||
|
style={{ marginRight: 20, marginLeft: 20 }}
|
||||||
|
>
|
||||||
|
<FontIcon
|
||||||
|
className={styleClasses.styleIconDetails}
|
||||||
|
iconName="Medal"
|
||||||
|
/>
|
||||||
|
<Label className={styleClasses.styleFieldLabel}>
|
||||||
|
About Me
|
||||||
|
</Label>
|
||||||
|
</Stack>
|
||||||
|
<Text
|
||||||
|
styles={styleTextField}
|
||||||
|
variant="medium"
|
||||||
|
block={true}
|
||||||
|
nowrap={true}
|
||||||
|
>
|
||||||
|
{userData.aboutMe ? userData.aboutMe.replace(/<[^>]+>/g, '').replace(/ /gi,' ') : "Not available"}
|
||||||
|
</Text>
|
||||||
|
<div className={styleClasses.separator}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "skills":
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styleClasses.styleField}`}
|
||||||
|
title={
|
||||||
|
userData.skills.join(",")
|
||||||
|
? userData.skills.join(",")
|
||||||
|
: "Not available"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
verticalAlign="center"
|
||||||
|
tokens={{ childrenGap: 5 }}
|
||||||
|
style={{ marginRight: 20, marginLeft: 20 }}
|
||||||
|
>
|
||||||
|
<FontIcon
|
||||||
|
className={styleClasses.styleIconDetails}
|
||||||
|
iconName="Education"
|
||||||
|
/>
|
||||||
|
<Label className={styleClasses.styleFieldLabel}>
|
||||||
|
Skills
|
||||||
|
</Label>
|
||||||
|
</Stack>
|
||||||
|
<Text
|
||||||
|
styles={styleTextField}
|
||||||
|
variant="medium"
|
||||||
|
block={true}
|
||||||
|
nowrap={true}
|
||||||
|
>
|
||||||
|
{userData.skills.join(",") ? (
|
||||||
|
<>
|
||||||
|
{userData.skills.join(",")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Not available"
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
<div className={styleClasses.separator}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
})}
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -15,5 +15,4 @@ export interface IUser {
|
||||||
officeLocation: string;
|
officeLocation: string;
|
||||||
postalCode: string;
|
postalCode: string;
|
||||||
userType: string;
|
userType: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface IUserBio {
|
||||||
|
id?: string;
|
||||||
|
aboutMe: string;
|
||||||
|
skills: string[];
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { IUser } from "./IUser";
|
import { IUser } from "./IUser";
|
||||||
import { IUserPresence } from "./IUserPresence";
|
import { IUserPresence } from "./IUserPresence";
|
||||||
export interface IUserExtended extends IUser, IUserPresence {
|
import { IUserBio } from "./IUserBio";
|
||||||
|
export interface IUserExtended extends IUser, IUserPresence, IUserBio {
|
||||||
count: number;
|
count: number;
|
||||||
pictureBase64: string;
|
pictureBase64: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ import "@pnp/graph/users";
|
||||||
import { IUserExtended } from "../entites/IUserExtended";
|
import { IUserExtended } from "../entites/IUserExtended";
|
||||||
import { IUser } from "../entites/IUser";
|
import { IUser } from "../entites/IUser";
|
||||||
import { IUserPresence } from "../entites/IUserPresence";
|
import { IUserPresence } from "../entites/IUserPresence";
|
||||||
|
import { IUserBio } from "../entites/IUserBio";
|
||||||
import { SPComponentLoader } from "@microsoft/sp-loader";
|
import { SPComponentLoader } from "@microsoft/sp-loader";
|
||||||
import { findIndex } from "lodash";
|
import { findIndex, join, values } from "lodash";
|
||||||
|
import { DetailsRow } from "office-ui-fabric-react";
|
||||||
|
|
||||||
/*************************************************************************************/
|
/*************************************************************************************/
|
||||||
// Hook to search users
|
// Hook to search users
|
||||||
|
@ -22,6 +24,7 @@ export const useSearchUsers = async (
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
): Promise<{ usersExtended: IUserExtended[]; nextPage: string }> => {
|
): Promise<{ usersExtended: IUserExtended[]; nextPage: string }> => {
|
||||||
pageSize = pageSize ? pageSize : 5;
|
pageSize = pageSize ? pageSize : 5;
|
||||||
|
|
||||||
const _searchResults: any = await _MSGraphClient
|
const _searchResults: any = await _MSGraphClient
|
||||||
.api('/users?$search="' + searchString + '"')
|
.api('/users?$search="' + searchString + '"')
|
||||||
.version("beta")
|
.version("beta")
|
||||||
|
@ -40,8 +43,11 @@ export const useSearchUsers = async (
|
||||||
for (const _user of _users) {
|
for (const _user of _users) {
|
||||||
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
||||||
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
||||||
|
const _userBio = await getUserBio(_user.id, _MSGraphClient);
|
||||||
|
|
||||||
_usersExtended.push({
|
_usersExtended.push({
|
||||||
..._user,
|
..._user,
|
||||||
|
..._userBio,
|
||||||
..._userPresence,
|
..._userPresence,
|
||||||
pictureBase64: _pictureBase64,
|
pictureBase64: _pictureBase64,
|
||||||
count: 0,
|
count: 0,
|
||||||
|
@ -77,14 +83,18 @@ export const useGetUsersByDepartment = async (
|
||||||
.count(true)
|
.count(true)
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
|
|
||||||
const _users: IUser[] = _searchResults.value;
|
const _users: IUser[] = _searchResults.value;
|
||||||
let _usersExtended: IUserExtended[] = [];
|
let _usersExtended: IUserExtended[] = [];
|
||||||
|
|
||||||
for (const _user of _users) {
|
for (const _user of _users) {
|
||||||
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
||||||
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
||||||
|
const _userBio = await getUserBio(_user.id, _MSGraphClient);
|
||||||
|
|
||||||
_usersExtended.push({
|
_usersExtended.push({
|
||||||
..._user,
|
..._user,
|
||||||
|
..._userBio,
|
||||||
..._userPresence,
|
..._userPresence,
|
||||||
pictureBase64: _pictureBase64,
|
pictureBase64: _pictureBase64,
|
||||||
count: 0,
|
count: 0,
|
||||||
|
@ -119,8 +129,10 @@ export const useGetUsersNextPage = async (
|
||||||
for (const _user of _users) {
|
for (const _user of _users) {
|
||||||
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
||||||
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
||||||
|
const _userBio = await getUserBio(_user.id, _MSGraphClient);
|
||||||
_usersExtended.push({
|
_usersExtended.push({
|
||||||
..._user,
|
..._user,
|
||||||
|
..._userBio,
|
||||||
..._userPresence,
|
..._userPresence,
|
||||||
pictureBase64: _pictureBase64,
|
pictureBase64: _pictureBase64,
|
||||||
count: 0,
|
count: 0,
|
||||||
|
@ -185,6 +197,21 @@ const getUserPresence = async (
|
||||||
return _presence;
|
return _presence;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//*************************************************************************************//
|
||||||
|
// function Get Users About Me and skillz
|
||||||
|
//*************************************************************************************//
|
||||||
|
|
||||||
|
const getUserBio = async (
|
||||||
|
userObjId,
|
||||||
|
_MSGraphClient
|
||||||
|
): Promise<IUserBio> => {
|
||||||
|
let _bio : IUserBio = await _MSGraphClient
|
||||||
|
.api("/users/{" + userObjId + "}?$select=aboutMe,skills")
|
||||||
|
.version("beta")
|
||||||
|
.get();
|
||||||
|
return _bio;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets user photo
|
* Gets user photo
|
||||||
* @param userId
|
* @param userId
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
"preconfiguredEntries": [{
|
"preconfiguredEntries": [{
|
||||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||||
"group": { "default": "SPFx Custom Web Parts" },
|
"group": { "default": "SPFx Custom Web Parts" },
|
||||||
"title": { "default": "Search Directory" },
|
"title": { "default": "UoB Search Directory" },
|
||||||
"description": { "default": "Search Directory" },
|
"description": { "default": "University of Brighton Search Directory" },
|
||||||
"officeFabricIconFontName": "ProfileSearch",
|
"officeFabricIconFontName": "ProfileSearch",
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Search Directory",
|
"title": "UoB Search Directory",
|
||||||
"maxHeight": 700,
|
"maxHeight": 700,
|
||||||
"showBox": true,
|
"showBox": true,
|
||||||
"refreshInterval": 3,
|
"refreshInterval": 3,
|
||||||
|
|
|
@ -42,8 +42,6 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
|
||||||
|
|
||||||
protected async onInit(): Promise<void> {
|
protected async onInit(): Promise<void> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this._themeProvider = this.context.serviceScope.consume(
|
this._themeProvider = this.context.serviceScope.consume(
|
||||||
ThemeProvider.serviceKey
|
ThemeProvider.serviceKey
|
||||||
);
|
);
|
||||||
|
@ -189,7 +187,6 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
|
||||||
PropertyFieldMultiSelect('userAttributes', {
|
PropertyFieldMultiSelect('userAttributes', {
|
||||||
key: 'userAttributes',
|
key: 'userAttributes',
|
||||||
label: strings.UserAttributesLabel,
|
label: strings.UserAttributesLabel,
|
||||||
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
key: "company",
|
key: "company",
|
||||||
|
@ -223,6 +220,14 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
|
||||||
key: "userType",
|
key: "userType",
|
||||||
text: "User Type"
|
text: "User Type"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "aboutMe",
|
||||||
|
text: "About Me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "skills",
|
||||||
|
text: "Skills"
|
||||||
|
},
|
||||||
],
|
],
|
||||||
selectedKeys: this.properties.userAttributes
|
selectedKeys: this.properties.userAttributes
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Reference in New Issue