Merge pull request #5 from pnp/master

FI
This commit is contained in:
Peter Paul Kirschner 2020-07-17 20:40:51 +02:00 committed by GitHub
commit f7fa93c031
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 21946 additions and 135 deletions

View File

@ -2,7 +2,7 @@
name: "\U0001F41E Bug" name: "\U0001F41E Bug"
about: Report an anomaly or unexpected behavior with a sample from this repository. about: Report an anomaly or unexpected behavior with a sample from this repository.
title: '' title: ''
labels: '' labels: type:bug
assignees: '' assignees: ''
--- ---

View File

@ -2,7 +2,7 @@
name: "\U0001F4A1 Suggestion" name: "\U0001F4A1 Suggestion"
about: Suggest an enhancement to make one of our web part samples even better about: Suggest an enhancement to make one of our web part samples even better
title: Let's make SAMPLE even better title: Let's make SAMPLE even better
labels: '' labels: type:enhancement
assignees: '' assignees: ''
--- ---

7
samples/.yo-rc.json Normal file
View File

@ -0,0 +1,7 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"whichFolder": "subdir"
}
}

View File

@ -45,7 +45,7 @@
Solution|Author(s) Solution|Author(s)
--------|--------- --------|---------
SPFx Collapsible Accordion Section|[Erik Benke](https://github.com/ejbenke) SPFx Collapsible Accordion Section|[Erik Benke](https://github.com/ejbenke) ([@erikjbenke](https://twitter.com/erikjbenke))
## Version history ## Version history

View File

@ -49,6 +49,7 @@ If your feed supports filtering by dates, you can specify `{s}` in the URL where
Solution|Author(s) Solution|Author(s)
--------|--------- --------|---------
react-calendar-feed | Hugo Bernier ([Tahoe Ninjas](http://tahoeninjas.blog), @bernierh) react-calendar-feed | Hugo Bernier ([Tahoe Ninjas](http://tahoeninjas.blog), @bernierh)
react-calendar-feed | Peter Paul Kirschner ([@petkir_at](https://twitter.com/petkir_at))
## Version history ## Version history
@ -60,6 +61,7 @@ Version|Date|Comments
4.0|January 16, 2019|Converted to SPFx 1.7.1; Removed NPM libraries associated with issue #708. 4.0|January 16, 2019|Converted to SPFx 1.7.1; Removed NPM libraries associated with issue #708.
5.0|August 17, 2019|Converted to SPFx 1.9.1; Refreshed carousel code; Addresses #735, #909. Also added **Convert from UTC** option to handle feeds which do not provide time zone information. 5.0|August 17, 2019|Converted to SPFx 1.9.1; Refreshed carousel code; Addresses #735, #909. Also added **Convert from UTC** option to handle feeds which do not provide time zone information.
5.1|April 16, 2020|Converted to SPFx 1.10.0; Fixed issue with UTC mode when in narrow view. Updated resizing behavior and styles to match OOB calendar view. Added support for themes and theme variants. 5.1|April 16, 2020|Converted to SPFx 1.10.0; Fixed issue with UTC mode when in narrow view. Updated resizing behavior and styles to match OOB calendar view. Added support for themes and theme variants.
5.2|July 15, 2020|Fixed issue to support IE11
## Disclaimer ## Disclaimer
@ -81,6 +83,7 @@ Version|Date|Comments
- Specify a maximum number of events to retrieve - Specify a maximum number of events to retrieve
- If necessary, specify to use a proxy. Use this option if you encounter issues where your feed provider does not accept your tenant URL as a CORS origin. - If necessary, specify to use a proxy. Use this option if you encounter issues where your feed provider does not accept your tenant URL as a CORS origin.
- If desired, specify how long (in minutes) you want to expire your users' local storage and refresh the events. - If desired, specify how long (in minutes) you want to expire your users' local storage and refresh the events.
- Exclude IE11 support with gulp parameter ```--NoIE11``` this is in-case-sensitive
## Features ## Features

View File

@ -3,7 +3,7 @@
"solution": { "solution": {
"name": "react-calendar-feed-client-side-solution", "name": "react-calendar-feed-client-side-solution",
"id": "25653136-fc83-4abe-b9d2-a4ac041959d5", "id": "25653136-fc83-4abe-b9d2-a4ac041959d5",
"version": "5.1.0.0", "version": "5.2.0.0",
"includeClientSideAssets": true "includeClientSideAssets": true
}, },
"paths": { "paths": {

View File

@ -2,6 +2,52 @@
const gulp = require('gulp'); const gulp = require('gulp');
const build = require('@microsoft/sp-build-web'); const build = require('@microsoft/sp-build-web');
const path = require('path');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
let needIESupport = true;
if (!!process.argv && process.argv.length > 0) {
needIESupport = process.argv.findIndex(item => '--noie11' === item.toLowerCase()) === -1;
}
if (needIESupport) {
const ie11BabeLoader = {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: {
"ie": "11"
}
}
]
]
}
};
//process.stdout.write(`Adding babel-loader to support IE11 \n`);
build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfiguration) => {
generatedConfiguration.module.rules.push({
test: /\.js$/,
/*
This selector increase the webpack(gulp serve) time 5 times then without
exclude: [/node_modules\/(?!(rss-parser))/],
*/
include: [
path.resolve(__dirname, "node_modules/rss-parser"),
],
use: [ie11BabeLoader]
});
return generatedConfiguration;
}
});
} else {
process.stdout.write(`No IE11 Support is set \n`);
}
build.initialize(gulp); build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "react-calendar-feed", "name": "react-calendar-feed",
"version": "5.1.0", "version": "5.2.0",
"private": true, "private": true,
"main": "lib/index.js", "main": "lib/index.js",
"engines": { "engines": {
@ -42,6 +42,8 @@
"@types/react": "16.8.8" "@types/react": "16.8.8"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.10.4",
"@babel/preset-env": "^7.10.4",
"@microsoft/rush-stack-compiler-2.9": "0.7.16", "@microsoft/rush-stack-compiler-2.9": "0.7.16",
"@microsoft/rush-stack-compiler-3.3": "0.3.5", "@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0", "@microsoft/sp-build-web": "1.10.0",
@ -51,6 +53,8 @@
"@types/chai": "3.4.34", "@types/chai": "3.4.34",
"@types/mocha": "2.2.38", "@types/mocha": "2.2.38",
"ajv": "~5.2.2", "ajv": "~5.2.2",
"gulp": "~3.9.1" "babel-loader": "^8.1.0",
"gulp": "~3.9.1",
"webpack": "^4.43.0"
} }
} }

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.9.1",
"libraryName": "react-graph-telephonedirectory",
"libraryId": "e1a26cc8-a086-454f-9f16-9aeb0a276bcc",
"environment": "spo",
"packageManager": "npm",
"isCreatingSolution": true,
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,70 @@
# Telephone Directory using React, Ms Graph and SPFx
## Summary
This is sample webpart using SPFx and MSGraph to fetch the users information based on First Name, Last Name and Email Address.
webpart will fetch data from directory using Graph API and display in details list.
![Telephone Directory using SPFx and Graph](./assets/Preview.gif)
## Used SharePoint Framework Version
![1.9.1](https://img.shields.io/badge/version-1.9.1-green.svg)
## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
* [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/overview)
* [Fluent UI](https://developer.microsoft.com/en-us/fluentui#/)
* [PnP Controls](https://pnp.github.io/sp-dev-fx-controls-react/)
## Prerequisites
> Define Graph API scope to Package-solution.json
![Graph API Scope](./assets/Capture.PNG)
## Solution
Solution|Author(s)
--------|---------
react-graph-telephonedirectory | [Dipen Shah](https://github.com/Dips365) ([@Dips_365](https://twitter.com/Dips_365))
## Version history
Version|Date|Comments
-------|----|--------
1.0|July 14,2020 | Initial Release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
* Clone this repository
* in the command line run:
* `npm install`
* `gulp build`
* `gulp bundle`
* `gulp package-solution`
* [Upload .sppkg to appcatalog](https://www.slideshare.net/Dipen038/upload-sp-solution)
* `Go to SharePoint Admin to grant access on graph API ` https://<Tenant>-admin.sharepoint.com/_layouts/15/online/AdminHome.aspx?source=sitecollections#/webApiPermissionManagement
* `gulp serve`
## Features
Demonstrates integration of SPFx and Graph API to search the organizational users information.
Description of the web part with possible additional details than in short summary.
This Web Part illustrates the following concepts on top of the SharePoint Framework:
* Detail List control from Fluent UI
* Webpart Title control from PnP React
* Microsoft Graph API
* External API Integration with SharePoint Framework
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-graph-telephonedirectory" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

View File

@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"telephonedirectory-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/telephonedirectory/TelephonedirectoryWebPart.js",
"manifest": "./src/webparts/telephonedirectory/TelephonedirectoryWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"TelephonedirectoryWebPartStrings": "lib/webparts/telephonedirectory/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-graph-telephonedirectory",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,23 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-graph-telephonedirectory-client-side-solution",
"id": "e1a26cc8-a086-454f-9f16-9aeb0a276bcc",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": true,
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "Directory.Read.All"
},
{
"resource": "Microsoft Graph",
"scope": "User.Read"
}
]
},
"paths": {
"zippedPackage": "solution/react-graph-telephonedirectory.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,7 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
{
"name": "react-graph-telephonedirectory",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@pnp/spfx-controls-react": "^1.19.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

View File

@ -0,0 +1,11 @@
import { MSGraphClient } from "@microsoft/sp-http";
import { IUserProperties } from "./IUserProperties";
/**
* Service to declare the methods
*/
export interface IMSGraphService{
getUserProperties(email:string,context:MSGraphClient):Promise<IUserProperties[]>;
getUserPropertiesBySearch(searchFor:string,client:MSGraphClient):Promise<IUserProperties[]>;
getUserPropertiesByFirstName(searchFor:string,client:MSGraphClient):Promise<IUserProperties[]>;
getUserPropertiesByLastName(searchFor:string,client:MSGraphClient):Promise<IUserProperties[]>;
}

View File

@ -0,0 +1,9 @@
export interface IUserProperties{
displayName:string;
email:string;
mobilePhone:string;
preferredLanguage:string;
JobTitle:string;
OfficeLocation:string;
businessPhone:string;
}

View File

@ -0,0 +1,116 @@
import { IMSGraphService } from "./IMSGraphService";
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { IUserProperties } from "./IUserProperties";
import { MSGraphClient,MSGraphClientFactory } from "@microsoft/sp-http";
import { Log } from "@microsoft/sp-core-library";
const LOG_SOURCE = "MSGraphService";
export class MSGraphService implements IMSGraphService{
public async getUserProperties(email:string,client:MSGraphClient):Promise<IUserProperties[]>{
let userProperties:IUserProperties[] = [];
try {
//let client:MSGraphClient = await context.msGraphClientFactory.getClient().then();
let endPoint:string = `/Users/${email}`;
let response = await client.api(`${endPoint}`).version("v1.0").get();
if(response){
userProperties.push({
businessPhone:response.businessPhones[0],
displayName:response.displayName,
email:response.mail,
JobTitle:response.jobTitle,
OfficeLocation:response.officeLocation,
mobilePhone:response.mobilePhone,
preferredLanguage:response.preferredLanguage
});
}
} catch (error) {
console.log(error);
Log.error(LOG_SOURCE+"getUserProperties():",error);
}
return userProperties;
}
public async getUserPropertiesByLastName(searchFor:string,client:MSGraphClient):Promise<IUserProperties[]>{
let userProperties:IUserProperties[] = [];
try {
let res = await client.api("users")
.version("v1.0")
.filter(`(startswith(surname,'${escape(searchFor)}'))`).get();
if(res.value.length !== 0){
res.value.map((_userProperty,_index)=>{
if(_userProperty.mail !== null){
userProperties.push({
businessPhone:_userProperty.businessPhones[0],
displayName:_userProperty.displayName,
email:_userProperty.mail,
JobTitle:_userProperty.jobTitle,
OfficeLocation:_userProperty.officeLocation,
mobilePhone:_userProperty.mobilePhone,
preferredLanguage:_userProperty.preferredLanguage
});
}
});
}
} catch (error) {
console.log(error);
Log.error(LOG_SOURCE+"getUserPropertiesByLastName():",error);
}
return userProperties;
}
public async getUserPropertiesByFirstName(searchFor:string,client:MSGraphClient):Promise<IUserProperties[]>{
let userProperties:IUserProperties[] = [];
try {
let res = await client.api("users")
.version("v1.0")
.filter(`(startswith(givenName,'${escape(searchFor)}'))`).get();
if(res.value.length !== 0){
res.value.map((_userProperty,_index)=>{
if(_userProperty.mail !== null){
userProperties.push({
businessPhone:_userProperty.businessPhones[0],
displayName:_userProperty.displayName,
email:_userProperty.mail,
JobTitle:_userProperty.jobTitle,
OfficeLocation:_userProperty.officeLocation,
mobilePhone:_userProperty.mobilePhone,
preferredLanguage:_userProperty.preferredLanguage
});
}
});
}
} catch (error) {
console.log(error);
Log.error(LOG_SOURCE+"getUserPropertiesBySearch():",error);
}
return userProperties;
}
public async getUserPropertiesBySearch(searchFor:string,client:MSGraphClient):Promise<IUserProperties[]>{
let userProperties:IUserProperties[] = [];
try {
let res = await client.api("users")
.version("v1.0")
.filter(`(startswith(displayName,'${escape(searchFor)}'))`).get();
if(res.value.length !== 0){
res.value.map((_userProperty,_index)=>{
userProperties.push({
businessPhone:_userProperty.businessPhones[0],
displayName:_userProperty.displayName,
email:_userProperty.mail,
JobTitle:_userProperty.jobTitle,
OfficeLocation:_userProperty.officeLocation,
mobilePhone:_userProperty.mobilePhone,
preferredLanguage:_userProperty.preferredLanguage
});
});
}
} catch (error) {
console.log(error);
Log.error(LOG_SOURCE+"getUserPropertiesBySearch():",error);
}
return userProperties;
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "586d82f7-79c3-4fc6-be7b-290fa1e4f1f6",
"alias": "TelephonedirectoryWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "telephonedirectory" },
"description": { "default": "Search user from organization using first name, last name and email address" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "telephonedirectory"
}
}]
}

View File

@ -0,0 +1,80 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'TelephonedirectoryWebPartStrings';
import Telephonedirectory from './components/Telephonedirectory';
import { ITelephoneDirectoryProps } from './components/ITelephonedirectoryProps';
import { MSGraphService } from '../../Services/MSGraphService';
import { MSGraphClient } from "@microsoft/sp-http";
export interface ITelephonedirectoryWebPartProps {
description: string;
Title:string;
}
export default class TelephonedirectoryWebPart extends BaseClientSideWebPart<ITelephonedirectoryWebPartProps> {
private MSGraphServiceInstance:MSGraphService;
private MSGraphClient:MSGraphClient;
public render(): void {
const element: React.ReactElement<ITelephoneDirectoryProps> = React.createElement(
Telephonedirectory,
{
description: this.properties.description,
MSGraphServiceInstance:this.MSGraphServiceInstance,
context:this.context,
MsGraphClient:this.MSGraphClient,
DisplayMode:this.displayMode,
WebpartTitle:strings.WebpartTitle,
updateProperty: (value: string) => {
this.properties.Title = value;
}
}
);
ReactDom.render(element, this.domElement);
}
protected async onInit(){
await super.onInit();
this.MSGraphServiceInstance = new MSGraphService();
this.MSGraphClient = await this.context.msGraphClientFactory.getClient();
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
}),
PropertyPaneTextField('Title',{
label: strings.Title
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,102 @@
import * as React from "react";
import { ByEmailProps } from "./ByEmailProps";
import styles from '../Telephonedirectory.module.scss';
import { ByEmailState } from "./ByEmailState";
import { autobind } from "office-ui-fabric-react/lib/Utilities";
import { PeoplePicker, PrincipalType } from '@pnp/spfx-controls-react/lib/PeoplePicker';
import { } from "@pnp/spfx-controls-react/";
import { Stack, IStackProps, IStackStyles } from 'office-ui-fabric-react/lib/Stack';
import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
const LOG_SOURCE = "ByFirstName";
const stackTokens = { childrenGap: 50 };
const iconProps = { iconName: 'Calendar' };
const stackStyles: Partial<IStackStyles> = { root: { width: 650 } };
const columnProps: Partial<IStackProps> = {
tokens: { childrenGap: 15 },
styles: { root: { width: 700 } },
};
export class ByEmail extends React.Component<ByEmailProps,ByEmailState>{
constructor(props:ByEmailProps){
super(props);
this.state={
loading:false,
searchFor: '',
userProperties:[],
isDataFound:true,
};
}
@autobind
private _getPeoplePickerItems(items: any[]) {
if(items.length == 1){
this.getUsers(items[0].secondaryText !== ""?items[0].secondaryText:items[0].id.split('|').pop());
}
else
{
this.setState({
userProperties:[]
});
}
}
@autobind
private async getUsers(email:string) : Promise<any>{
this.setState({loading:true},async()=>{
await this.props.MSGraphServiceInstance
.getUserProperties(email,this.props.MSGraphClient)
// tslint:disable-next-line: no-shadowed-variable
.then((users)=>{
if(users.length !== 0){
this.setState({
userProperties:users,
isDataFound:true
});
}
else
{
this.setState({
userProperties:[],
isDataFound:false
});
}
});
});
}
public render(): React.ReactElement<ByEmailProps> {
return (
<div className={styles.telephonedirectory}>
<div>
<Stack horizontal tokens={stackTokens} styles={stackStyles}>
<Stack {...columnProps}>
<PeoplePicker
context={this.props.context}
placeholder=""
titleText="Email"
personSelectionLimit={1}
showtooltip={false}
disabled={false}
selectedItems={this._getPeoplePickerItems}
principalTypes={[PrincipalType.User]}
resolveDelay={1000} />
</Stack>
</Stack>
<div>
</div>
<div id='detailedList'>
{this.state.userProperties.length !== 0 &&
<DetailsList
items={this.state.userProperties}
columns={this.props.columns}
isHeaderVisible={true}
layoutMode={DetailsListLayoutMode.justified}
/>
}
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,10 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { MSGraphService } from "../../../../Services/MSGraphService";
import { MSGraphClient } from "@microsoft/sp-http";
import { IColumn } from "office-ui-fabric-react/lib/DetailsList";
export interface ByEmailProps{
context:WebPartContext;
MSGraphServiceInstance:MSGraphService;
MSGraphClient:MSGraphClient;
columns:IColumn[];
}

View File

@ -0,0 +1,10 @@
import { IUserProperties } from "../../../../Services/IUserProperties";
import { IColumn } from "office-ui-fabric-react/lib/DetailsList";
export interface ByEmailState{
loading:boolean;
userProperties:IUserProperties[];
searchFor: string;
isDataFound:boolean;
}

View File

@ -0,0 +1,105 @@
import * as React from "react";
import { ByFirstNameProps } from "./ByFirstNameProps";
import styles from '../Telephonedirectory.module.scss';
import { ByFirstNameState } from "./ByFirstNameState";
import { autobind } from "office-ui-fabric-react/lib/Utilities";
import * as strings from 'TelephonedirectoryWebPartStrings';
import { Log } from "@microsoft/sp-core-library";
import { Stack, IStackProps, IStackStyles } from 'office-ui-fabric-react/lib/Stack';
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
const LOG_SOURCE = "ByFirstName";
const stackTokens = { childrenGap: 50 };
const stackStyles: Partial<IStackStyles> = { root: { width: 650 } };
const columnProps: Partial<IStackProps> = {
tokens: { childrenGap: 15 },
styles: { root: { width: 700 } },
};
export class ByFirstName extends React.Component<ByFirstNameProps,ByFirstNameState>{
constructor(props:ByFirstNameProps){
super(props);
this.state={
loading:false,
searchFor: '',
userProperties:[],
isDataFound:true,
};
}
@autobind
private searchUsers(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void {
try {
this.setState({
searchFor: newValue,
});
this.getUsers(newValue);
} catch (error) {
Log.error(LOG_SOURCE,error);
}
}
@autobind
private async getUsers(email:string) : Promise<any>{
this.setState({loading:true},async()=>{
await this.props.MSGraphServiceInstance
.getUserPropertiesByFirstName(email,this.props.MSGraphClient)
// tslint:disable-next-line: no-shadowed-variable
.then((users)=>{
if(users.length !== 0){
this.setState({
userProperties:users,
isDataFound:true
});
}
else
{
this.setState({
userProperties:[],
isDataFound:false
});
}
});
});
}
@autobind
private searchUsersError(value: string): string {
// The search for text cannot contain spaces
return (value == null || value.length == 0 || value.indexOf(" ") < 0)
? ''
: 'Nothing matched';
}
public render(): React.ReactElement<ByFirstNameProps> {
return (
<div className={styles.telephonedirectory}>
<div>
<Stack horizontal tokens={stackTokens} styles={stackStyles}>
<Stack {...columnProps}>
<TextField
label={strings.SearchUserByFirstName}
required={false}
value={this.state.searchFor}
onChange={this.searchUsers}
onGetErrorMessage={this.searchUsersError}
/>
</Stack>
</Stack>
<div>
</div>
<div id='detailedList'>
{this.state.userProperties.length !== 0 &&
<DetailsList
items={this.state.userProperties}
columns={this.props.Columns}
isHeaderVisible={true}
layoutMode={DetailsListLayoutMode.justified}
/>
}
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,10 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { MSGraphService } from "../../../../Services/MSGraphService";
import { MSGraphClient } from "@microsoft/sp-http";
import { IColumn } from "office-ui-fabric-react/lib/DetailsList";
export interface ByFirstNameProps{
context:WebPartContext;
MSGraphServiceInstance:MSGraphService;
MSGraphClient:MSGraphClient;
Columns:IColumn[];
}

View File

@ -0,0 +1,10 @@
import { IUserProperties } from "../../../../Services/IUserProperties";
import { IColumn } from "office-ui-fabric-react/lib/DetailsList";
export interface ByFirstNameState{
loading:boolean;
userProperties:IUserProperties[];
searchFor: string;
isDataFound:boolean;
}

View File

@ -0,0 +1,108 @@
import * as React from "react";
import { ByLastNameProps } from "./ByLastNameProps";
import styles from '../Telephonedirectory.module.scss';
import { ByLastNameState } from "./ByLastNameState";
import { autobind } from "office-ui-fabric-react/lib/Utilities";
import * as strings from 'TelephonedirectoryWebPartStrings';
import { Log } from "@microsoft/sp-core-library";
import { Stack, IStackProps, IStackStyles } from 'office-ui-fabric-react/lib/Stack';
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
const LOG_SOURCE = "ByFirstName";
const stackTokens = { childrenGap: 50 };
const iconProps = { iconName: 'Calendar' };
const stackStyles: Partial<IStackStyles> = { root: { width: 650 } };
const columnProps: Partial<IStackProps> = {
tokens: { childrenGap: 15 },
styles: { root: { width: 700 } },
};
export class ByLastName extends React.Component<ByLastNameProps,ByLastNameState>{
constructor(props:ByLastNameProps)
{
super(props);
this.state={
loading:false,
searchFor: '',
userProperties:[],
isDataFound:true,
};
}
@autobind
private searchUsers(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void {
try {
this.setState({
searchFor: newValue,
});
this.getUsers(newValue);
} catch (error) {
Log.error(LOG_SOURCE,error);
}
}
@autobind
private async getUsers(email:string) : Promise<any>{
this.setState({loading:true},async()=>{
await this.props.MSGraphServiceInstance
.getUserPropertiesByLastName(email,this.props.MSGraphClient)
// tslint:disable-next-line: no-shadowed-variable
.then((users)=>{
if(users.length !== 0){
this.setState({
userProperties:users,
isDataFound:true
});
}
else
{
this.setState({
userProperties:[],
isDataFound:false
});
}
});
});
}
@autobind
private searchUsersError(value: string): string {
// The search for text cannot contain spaces
return (value == null || value.length == 0 || value.indexOf(" ") < 0)
? ''
: 'Nothing matched';
}
public render(): React.ReactElement<ByLastNameProps> {
return (
<div className={styles.telephonedirectory}>
<div>
<Stack horizontal tokens={stackTokens} styles={stackStyles}>
<Stack {...columnProps}>
<TextField
label={strings.SearchUserByLastName}
required={false}
value={this.state.searchFor}
onChange={this.searchUsers}
onGetErrorMessage={this.searchUsersError}
/>
</Stack>
</Stack>
<div>
</div>
<div id='detailedList'>
{this.state.userProperties.length !== 0 &&
<DetailsList
items={this.state.userProperties}
columns={this.props.columns}
isHeaderVisible={true}
layoutMode={DetailsListLayoutMode.justified}
/>
}
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,11 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { MSGraphService } from "../../../../Services/MSGraphService";
import { MSGraphClient } from "@microsoft/sp-http";
import { IColumn } from "office-ui-fabric-react/lib/DetailsList";
export interface ByLastNameProps{
context:WebPartContext;
MSGraphServiceInstance:MSGraphService;
MSGraphClient:MSGraphClient;
columns:IColumn[];
}

View File

@ -0,0 +1,8 @@
import { IUserProperties } from "../../../../Services/IUserProperties";
import { IColumn } from "office-ui-fabric-react/lib/DetailsList";
export interface ByLastNameState{
loading:boolean;
userProperties:IUserProperties[];
searchFor: string;
isDataFound:boolean;
}

View File

@ -0,0 +1,8 @@
import { IUserProperties } from "../../../Services/IUserProperties";
import { IColumn } from "office-ui-fabric-react/lib/DetailsList";
export interface ITelephoneDirectoryState{
loading:boolean;
columns:IColumn[];
selectedKey:string;
}

View File

@ -0,0 +1,13 @@
import { MSGraphService } from "../../../Services/MSGraphService";
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { MSGraphClient } from "@microsoft/sp-http";
import { DisplayMode } from '@microsoft/sp-core-library';
export interface ITelephoneDirectoryProps {
description: string;
MSGraphServiceInstance:MSGraphService;
context:WebPartContext;
MsGraphClient:MSGraphClient;
DisplayMode:DisplayMode;
WebpartTitle:string;
updateProperty: (value: string) => void;
}

View File

@ -0,0 +1,78 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.telephonedirectory {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.errorMessage{
color: red($color: #000000);
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,161 @@
import * as React from 'react';
import styles from './Telephonedirectory.module.scss';
import { ITelephoneDirectoryProps } from './ITelephonedirectoryProps';
import { ITelephoneDirectoryState } from "./ITelephoneDirectoryState";
import * as strings from 'TelephonedirectoryWebPartStrings';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import { ByFirstName } from "./ByFirstName/ByFirstName";
import { ByLastName } from "./ByLastName/ByLastName";
import { ByEmail } from "./ByEmail/ByEmail";
import { Pivot, PivotItem,PivotLinkFormat, PivotLinkSize } from 'office-ui-fabric-react/lib/Pivot';
import { IColumn } from 'office-ui-fabric-react/lib/DetailsList';
const LOG_SOURCE = "TelephoneDirectory";
export default class Telephonedirectory extends React.Component<ITelephoneDirectoryProps, ITelephoneDirectoryState> {
private headers = [
{ label: 'Name', key: 'displayName' },
{ label: 'Email', key: 'email' },
{ label:'Mobile Phone',key:'mobilePhone'},
{ label:'JobTitle',key:'JobTitle'},
{ label:'OfficeLocation',key:'OfficeLocation'},
{ label:'Business Phone',key:'businessPhone'
}];
constructor(props:ITelephoneDirectoryProps){
super(props);
const columns: IColumn[] = [
{
key: 'column1',
name: strings.DisplayName,
isRowHeader: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
fieldName: 'displayName',
minWidth: 100,
maxWidth: 300,
isResizable: false
},
{
key: 'column2',
name: strings.Email,
fieldName: 'email',
isSorted: true,
isSortedDescending: false,
minWidth: 200,
maxWidth: 300,
isResizable: false
},
{
key: 'column3',
name: strings.MobilePhone,
fieldName: 'mobilePhone',
isSorted: true,
isSortedDescending: false,
minWidth: 200,
maxWidth: 300,
isResizable: false
},
{
key: 'column5',
name: strings.JobTitle,
fieldName: 'JobTitle',
isSorted: true,
isSortedDescending: false,
minWidth: 200,
maxWidth: 300,
isResizable: false
},
{
key: 'column6',
name: strings.OfficeLocation,
fieldName: 'OfficeLocation',
isSorted: true,
isSortedDescending: false,
minWidth: 100,
maxWidth: 300,
isResizable: true
},
{
key: 'column7',
name: strings.businessPhone,
fieldName: 'businessPhone',
isSorted: true,
isSortedDescending: false,
minWidth: 100,
maxWidth: 300,
isResizable: true
}
];
this.state={
loading:false,
selectedKey:"byFirstName",
columns:columns
};
}
private _handleLinkClick = (item: PivotItem): void => {
this.setState({
selectedKey: item.props.itemKey
});
}
public render(): React.ReactElement<ITelephoneDirectoryProps> {
return(
<div className={ styles.telephonedirectory }>
<div>
<div>
<div>
<WebPartTitle displayMode={this.props.DisplayMode}
title={this.props.WebpartTitle}
updateProperty={this.props.updateProperty} />
<Pivot headersOnly={true}
selectedKey ={this.state.selectedKey}
onLinkClick = {this._handleLinkClick}
linkSize={PivotLinkSize.normal}
linkFormat={PivotLinkFormat.tabs}>
<PivotItem
headerText='Search User By First Name'
itemKey='byFirstName'
itemIcon="Group" ></PivotItem>
<PivotItem
headerText='Search User By Last Name'
itemKey='byLastName'
itemIcon="Group"></PivotItem>
<PivotItem
headerText='Search User By Email'
itemKey="byEmail"
itemIcon="Group"></PivotItem>
</Pivot><br/>
{this.state.selectedKey === "byFirstName" &&
<ByFirstName
MSGraphClient={this.props.MsGraphClient}
MSGraphServiceInstance={this.props.MSGraphServiceInstance}
context={this.props.context}
Columns={this.state.columns}></ByFirstName>
}
{this.state.selectedKey === "byLastName" &&
<ByLastName
MSGraphClient = {this.props.MsGraphClient}
MSGraphServiceInstance= {this.props.MSGraphServiceInstance}
context={this.props.context}
columns={this.state.columns}></ByLastName>
}
{this.state.selectedKey === "byEmail" &&
<ByEmail
MSGraphClient = {this.props.MsGraphClient}
MSGraphServiceInstance= {this.props.MSGraphServiceInstance}
context={this.props.context}
columns={this.state.columns}></ByEmail>
}
</div>
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,18 @@
define([], function() {
return {
"PropertyPaneDescription": "Beschreibung",
"BasicGroupName": "Gruppenname",
"DescriptionFieldLabel": "Beschreibung Feld",
"SearchUser":"Benutzer nach Vorname suchen",
"SearchUserByFirstName":"Benutzer nach Vorname suchen",
"SearchUserByLastName":"Suche Benutzer nach Nachname",
"DisplayName":"Anzeigename",
"Email":"Email",
"MobilePhone":"Mobiltelefon",
"JobTitle":"Berufsbezeichnung",
"OfficeLocation":"Bürostandort",
"businessPhone":"Geschäftstelefon",
"WebpartTitle":"Telefonbuch",
"Title":"Titel"
}
});

View File

@ -0,0 +1,18 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field",
"SearchUser":"Search User by First Name",
"SearchUserByFirstName":"Search User by First Name",
"SearchUserByLastName":"Search User by Last Name",
"DisplayName":"Display Name",
"Email":"Email",
"MobilePhone":"Mobile Phone",
"JobTitle":"Job Title",
"OfficeLocation":"Office Location",
"businessPhone":"Business Phone",
"WebpartTitle":"Telephone Directory",
"Title":"Title"
}
});

View File

@ -0,0 +1,22 @@
declare interface ITelephonedirectoryWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
SearchUser:string;
SearchUserByFirstName:string;
SearchUserByLastName:string;
DisplayName:string;
Email:string;
MobilePhone:string;
JobTitle:string;
OfficeLocation:string;
businessPhone:string;
WebpartTitle:string;
Title:string;
placeholder:string;
}
declare module 'TelephonedirectoryWebPartStrings' {
const strings: ITelephonedirectoryWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,38 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}