commit new webpart
This commit is contained in:
parent
57ec332b73
commit
e5c801b953
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": ["*.js"],
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended"
|
||||
]
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "onprem19",
|
||||
"version": "1.12.0",
|
||||
"libraryName": "react-organization-chart",
|
||||
"libraryId": "0b4a3e5d-123f-41ea-96c4-538c6a19932b",
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
# react-organization-chart
|
||||
|
||||
## Summary
|
||||
|
||||
Short summary on functionality and used technologies.
|
||||
|
||||
[picture of the solution in action, if possible]
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![version](https://img.shields.io/badge/version-1.12-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
- [SharePoint Framework](https://aka.ms/spfx)
|
||||
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
|
||||
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
> Any special pre-requisites?
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
folder name | Author details (name, company, twitter alias with link)
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.1|March 10, 2021|Update comment
|
||||
1.0|January 29, 2021|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
|
||||
- Ensure that you are at the solution folder
|
||||
- in the command-line run:
|
||||
- **npm install**
|
||||
- **gulp serve**
|
||||
|
||||
> Include any additional steps as needed.
|
||||
|
||||
## Features
|
||||
|
||||
Description of the extension that expands upon high-level summary above.
|
||||
|
||||
This extension illustrates the following concepts:
|
||||
|
||||
- topic 1
|
||||
- topic 2
|
||||
- topic 3
|
||||
|
||||
> Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance.
|
||||
|
||||
> Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp.
|
||||
|
||||
## References
|
||||
|
||||
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
- [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
|
||||
- [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
|
||||
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
|
||||
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"organization-chart-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/organizationChart/OrganizationChartWebPart.js",
|
||||
"manifest": "./src/webparts/organizationChart/OrganizationChartWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"OrganizationChartWebPartStrings": "lib/webparts/organizationChart/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -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-organization-chart",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-organization-chart",
|
||||
"id": "0b4a3e5d-123f-41ea-96c4-538c6a19932b",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true
|
||||
|
||||
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-organization-chart.sppkg"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// gulpfile.js
|
||||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
const merge = require('webpack-merge');
|
||||
const TerserPlugin = require('terser-webpack-plugin-legacy');
|
||||
build.addSuppression(/Warning - \[sass\] The local CSS class .* is not camelCase and will not be type-safe./gi);
|
||||
|
||||
// force use of projects specified typescript version
|
||||
const typeScriptConfig = require('@microsoft/gulp-core-build-typescript/lib/TypeScriptConfiguration');
|
||||
typeScriptConfig.TypeScriptConfiguration.setTypescriptCompiler(require('typescript'));
|
||||
|
||||
|
||||
|
||||
|
||||
build.tslint.enabled = false;
|
||||
|
||||
|
||||
|
||||
const eslint = require('gulp-eslint');
|
||||
|
||||
const eslintSubTask = build.subTask('eslint', function (gulp, buildOptions, done) {
|
||||
return gulp.src(['src/**/*.{ts,tsx}'])
|
||||
// eslint() attaches the lint output to the "eslint" property
|
||||
// of the file object so it can be used by other modules.
|
||||
.pipe(eslint())
|
||||
// eslint.format() outputs the lint results to the console.
|
||||
// Alternatively use eslint.formatEach() (see Docs).
|
||||
.pipe(eslint.format())
|
||||
// To have the process exit with an error code (1) on
|
||||
// lint error, return the stream and pipe to failAfterError last.
|
||||
.pipe(eslint.failAfterError());
|
||||
});
|
||||
|
||||
build.rig.addPreBuildTask(build.task('eslint-task', eslintSubTask));
|
||||
|
||||
/* build.configureWebpack.setConfig({
|
||||
additionalConfiguration: function (config) {
|
||||
let newConfig = config;
|
||||
config.plugins.forEach((plugin, i) => {
|
||||
if (plugin.options && plugin.options.mangle) {
|
||||
config.plugins.splice(i, 1);
|
||||
newConfig = merge(config, {
|
||||
plugins: [
|
||||
new TerserPlugin()
|
||||
]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return newConfig;
|
||||
}
|
||||
}); */
|
||||
|
||||
// force use of projects specified react version
|
||||
build.configureWebpack.mergeConfig({
|
||||
|
||||
additionalConfiguration: (generatedConfiguration) => {
|
||||
// force use of projects specified react version
|
||||
generatedConfiguration.externals = generatedConfiguration.externals
|
||||
.filter(name => !(["react", "react-dom"].includes(name)));
|
||||
/* generatedConfiguration.externals = generatedConfiguration.externals
|
||||
.filter(name => !(["@fluentui/react"].includes(name))); */
|
||||
|
||||
// force use TerserPlugIn (remove UglifyJs)
|
||||
generatedConfiguration.plugins.forEach((plugin, i) => {
|
||||
if (plugin.options && plugin.options.mangle) {
|
||||
generatedConfiguration.plugins.splice(i, 1);
|
||||
generatedConfiguration = merge(generatedConfiguration, {
|
||||
plugins: [
|
||||
new TerserPlugin()
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
return generatedConfiguration;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "react-organization-chart",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/theme": "^2.1.0",
|
||||
"@microsoft/office-ui-fabric-react-bundle": "^1.11.0",
|
||||
"@microsoft/sp-core-library": "~1.4.1",
|
||||
"@microsoft/sp-lodash-subset": "~1.4.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "^1.11.0",
|
||||
"@microsoft/sp-tslint-rules": "^1.11.0",
|
||||
"@microsoft/sp-webpart-base": "~1.4.1",
|
||||
"@pnp/common": "^1.3.11",
|
||||
"@pnp/logging": "^1.3.11",
|
||||
"@pnp/odata": "^1.3.11",
|
||||
"@pnp/sp": "^1.3.11",
|
||||
"@pnp/spfx-controls-react": "^1.21.1",
|
||||
"@pnp/spfx-property-controls": "^1.3.0",
|
||||
"@uifabric/merge-styles": "^7.19.2",
|
||||
"enhanced-resolve": "^5.8.0",
|
||||
"idb-keyval": "^5.0.5",
|
||||
"office-ui-fabric-react": "^6.214.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"spfx-uifabric-themes": "^0.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.4.1",
|
||||
"@microsoft/sp-module-interfaces": "1.4.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.4.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/mocha": "2.2.38",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"ajv": "~5.2.2",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"gulp": "~3.9.1",
|
||||
"gulp-eslint": "^6.0.0",
|
||||
"terser-webpack-plugin-legacy": "^1.2.3",
|
||||
"typescript": "3.9.7",
|
||||
"webpack-merge": "^4.2.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import { SPComponentLoader } from "@microsoft/sp-loader";
|
||||
|
||||
// get all the web's regional settings
|
||||
|
||||
const DEFAULT_PERSONA_IMG_HASH = "7ad602295f8386b7615b582d87bcc294";
|
||||
const DEFAULT_IMAGE_PLACEHOLDER_HASH = "4a48f26592f4e1498d7a478a4c48609c";
|
||||
const MD5_MODULE_ID = "8494e7d7-6b99-47b2-a741-59873e42f16f";
|
||||
const PROFILE_IMAGE_URL = "/_layouts/15/userphoto.aspx?size=M&accountname=";
|
||||
|
||||
/**
|
||||
* Gets user photo
|
||||
* @param userId
|
||||
* @returns user photo
|
||||
*/
|
||||
export const getUserPhoto = async (userId: string): Promise<string> => {
|
||||
|
||||
|
||||
const personaImgUrl = PROFILE_IMAGE_URL + userId;
|
||||
|
||||
// tslint:disable-next-line: no-use-before-declare
|
||||
const url: string = await getImageBase64(personaImgUrl);
|
||||
|
||||
const newHash = await getMd5HashForUrl(url);
|
||||
|
||||
if (
|
||||
newHash !== DEFAULT_PERSONA_IMG_HASH &&
|
||||
newHash !== DEFAULT_IMAGE_PLACEHOLDER_HASH
|
||||
) {
|
||||
return "data:image/png;base64," + url;
|
||||
} else {
|
||||
return "undefined";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get MD5Hash for the image url to verify whether user has default image or custom image
|
||||
* @param url
|
||||
*/
|
||||
export const getMd5HashForUrl = async (url: string): Promise<string> => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const library : any = await loadSPComponentById(MD5_MODULE_ID) ;
|
||||
try {
|
||||
const md5Hash = library.Md5Hash;
|
||||
if (md5Hash) {
|
||||
const convertedHash = md5Hash(url);
|
||||
return convertedHash;
|
||||
}
|
||||
} catch (error) {
|
||||
return url;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load SPFx component by id, SPComponentLoader is used to load the SPFx components
|
||||
* @param componentId - componentId, guid of the component library
|
||||
*/
|
||||
export const loadSPComponentById = async (
|
||||
componentId: string
|
||||
): Promise<unknown> => {
|
||||
const component: unknown = SPComponentLoader.loadComponentById(componentId)
|
||||
return component;
|
||||
};
|
||||
/**
|
||||
* Gets image base64
|
||||
* @param pictureUrl
|
||||
* @returns image base64
|
||||
*/
|
||||
export const getImageBase64 = async (pictureUrl: string): Promise<string> => {
|
||||
console.log(pictureUrl);
|
||||
return new Promise((resolve) => {
|
||||
const image = new Image();
|
||||
image.addEventListener("load", () => {
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
(tempCanvas.width = image.width),
|
||||
(tempCanvas.height = image.height),
|
||||
tempCanvas.getContext("2d").drawImage(image, 0, 0);
|
||||
let base64Str;
|
||||
try {
|
||||
base64Str = tempCanvas.toDataURL("image/png");
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
base64Str = base64Str.replace(/^data:image\/png;base64,/, "");
|
||||
resolve(base64Str);
|
||||
});
|
||||
image.src = pictureUrl;
|
||||
});
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
import {
|
||||
Stack,
|
||||
DocumentCard,
|
||||
DocumentCardDetails,
|
||||
PersonaSize,
|
||||
DocumentCardActions,
|
||||
IButtonProps,
|
||||
} from "office-ui-fabric-react";
|
||||
import * as React from "react";
|
||||
import { Person } from "../Person/Person";
|
||||
import { ICompactCardProps } from "./ICompactCardProps";
|
||||
import { useHoverCardStyles } from "./useHoverCardStyles";
|
||||
|
||||
export const CompactCard: React.FunctionComponent<ICompactCardProps> = (
|
||||
props: ICompactCardProps
|
||||
) => {
|
||||
const { user } = props;
|
||||
const {
|
||||
stackPersonaStyles,
|
||||
hoverCardStyles,
|
||||
buttonStylesHouver,
|
||||
documentCardActionStyles
|
||||
} = useHoverCardStyles();
|
||||
|
||||
const documentCardActionsHouver: IButtonProps[] = React.useMemo(() =>{
|
||||
const actions:IButtonProps[] = [] ;
|
||||
actions.push(
|
||||
{
|
||||
iconProps: { iconName: "Chat" },
|
||||
title: "Chat",
|
||||
styles: buttonStylesHouver,
|
||||
onClick: (ev)=>{
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
window.open(`https://teams.microsoft.com/l/chat/0/0?users=${user.email}&message=Hi ${user.displayName} `,"_blank");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (user?.workPhone){
|
||||
actions.push(
|
||||
{
|
||||
iconProps: { iconName: "Phone" },
|
||||
title: "Call",
|
||||
styles: buttonStylesHouver,
|
||||
onClick: (ev)=> {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
window.open(`CALLTO:${user.workPhone}`,"_blank");
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
return actions;
|
||||
},[buttonStylesHouver, user.displayName, user.email, user.workPhone]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
tokens={{ childrenGap: 10 }}
|
||||
horizontalAlign="start"
|
||||
verticalAlign="center"
|
||||
|
||||
>
|
||||
<DocumentCard className={hoverCardStyles.hoverHeader}>
|
||||
<DocumentCardDetails>
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="space-between"
|
||||
styles={stackPersonaStyles}
|
||||
>
|
||||
<Person
|
||||
text={user.displayName}
|
||||
secondaryText={user.title}
|
||||
tertiaryText={user.department}
|
||||
userEmail={user.email}
|
||||
pictureUrl={user.pictureUrl}
|
||||
size={PersonaSize.size72}
|
||||
/>
|
||||
</Stack>
|
||||
<DocumentCardActions actions={documentCardActionsHouver} styles={documentCardActionStyles} />
|
||||
</DocumentCardDetails>
|
||||
</DocumentCard>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,140 @@
|
|||
import {
|
||||
Stack,
|
||||
FontIcon,
|
||||
Text,
|
||||
IStackTokens,
|
||||
PersonaSize,
|
||||
Link,
|
||||
} from "office-ui-fabric-react"
|
||||
import * as React from "react";
|
||||
import { Person } from "../Person/Person";
|
||||
import { IExpandedCardProps } from "./IExpandedCardProps";
|
||||
import { useHoverCardStyles } from "./useHoverCardStyles";
|
||||
import {
|
||||
useGetUserProperties,
|
||||
manpingUserProperties,
|
||||
} from "../../hooks/useGetUserProperties";
|
||||
import { IUserInfo } from "../../models/IUserInfo";
|
||||
export const ExpandedCard: React.FunctionComponent<IExpandedCardProps> = (
|
||||
props: IExpandedCardProps
|
||||
) => {
|
||||
const { user } = props;
|
||||
const { expandedCardStackStyle, hoverCardStyles } = useHoverCardStyles();
|
||||
|
||||
const stackFieldTokens: IStackTokens = {
|
||||
childrenGap: 15,
|
||||
|
||||
};
|
||||
|
||||
const { getUserProfile } = useGetUserProperties();
|
||||
const [manager, setManager] = React.useState<IUserInfo>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!user.manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const { currentUserProfile } = await getUserProfile(user.manager);
|
||||
const wManager: IUserInfo = await manpingUserProperties(
|
||||
currentUserProfile
|
||||
);
|
||||
setManager(wManager);
|
||||
})();
|
||||
}, [getUserProfile, user.manager]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack tokens={{ childrenGap: 10 }} styles={expandedCardStackStyle}>
|
||||
<Text variant="medium" style={{ fontWeight: 600 }}>
|
||||
Contact
|
||||
</Text>
|
||||
{
|
||||
user.email && (
|
||||
<>
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="start"
|
||||
verticalAlign="center"
|
||||
styles={{root:{padding: 5}}}
|
||||
tokens={stackFieldTokens}
|
||||
>
|
||||
<FontIcon iconName="mail" className={hoverCardStyles.iconStyles} />
|
||||
<Link href={`MAILTO:${user.email}`} target="_blank"
|
||||
data-interception="off">
|
||||
<Text variant="smallPlus">{user.email}</Text>
|
||||
</Link>
|
||||
</Stack>
|
||||
<div className={hoverCardStyles.separatorHorizontal}></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{user.workPhone && (
|
||||
<>
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="start"
|
||||
verticalAlign="center"
|
||||
tokens={stackFieldTokens}
|
||||
styles={{root:{padding: 5}}}
|
||||
>
|
||||
<FontIcon
|
||||
iconName="Phone"
|
||||
className={hoverCardStyles.iconStyles}
|
||||
/>
|
||||
<Link href={`CALLTO:${user.workPhone}`} target="_blank"
|
||||
data-interception="off">
|
||||
<Text variant="smallPlus">{user.workPhone}</Text>
|
||||
</Link>
|
||||
</Stack>
|
||||
<div className={hoverCardStyles.separatorHorizontal}></div>
|
||||
</>
|
||||
)}
|
||||
{user.location && (
|
||||
<>
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="start"
|
||||
verticalAlign="center"
|
||||
tokens={stackFieldTokens}
|
||||
styles={{root:{padding: 5}}}
|
||||
>
|
||||
<FontIcon
|
||||
iconName="MapPin"
|
||||
className={hoverCardStyles.iconStyles}
|
||||
/>
|
||||
<Link
|
||||
href={`https://www.bing.com/maps?q=${encodeURIComponent(
|
||||
user.location
|
||||
)}`}
|
||||
target="_blank"
|
||||
data-interception="off"
|
||||
>
|
||||
<Text variant="smallPlus">{user.location}</Text>
|
||||
</Link>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
|
||||
{manager && (
|
||||
<>
|
||||
<Text
|
||||
variant="medium"
|
||||
style={{ fontWeight: 600, marginTop: 25, marginBottom: 10 }}
|
||||
>
|
||||
Reports to
|
||||
</Text>
|
||||
<Person
|
||||
userEmail={manager.email}
|
||||
size={PersonaSize.size48}
|
||||
pictureUrl={manager.pictureUrl}
|
||||
text={manager.displayName}
|
||||
secondaryText={manager.title}
|
||||
></Person>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
import { IUserInfo } from "../../models/IUserInfo";
|
||||
|
||||
export interface ICompactCardProps {
|
||||
user: IUserInfo
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { IUserInfo } from "../../models/IUserInfo";
|
||||
|
||||
export interface IExpandedCardProps {
|
||||
user: IUserInfo
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export * from './CompactCard';
|
||||
export * from './ExpandedCard';
|
||||
export * from './ICompactCardProps';
|
||||
export * from './IExpandedCardProps';
|
||||
export * from './useHoverCardStyles';
|
|
@ -0,0 +1,73 @@
|
|||
import { IButtonStyles, IDocumentCardActionsStyles, IStackStyles, mergeStyles, mergeStyleSets } from "office-ui-fabric-react";
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import Theme from "spfx-uifabric-themes";
|
||||
const currentTheme = window.__themeState__.theme;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useHoverCardStyles = () => {
|
||||
|
||||
const stackPersonaStyles: Partial<IStackStyles> = {
|
||||
root: { padding: 15 },
|
||||
};
|
||||
const stackCompactStyles: Partial<IStackStyles> = {
|
||||
root: { padding: 15 },
|
||||
};
|
||||
|
||||
const documentCardActionStyles: Partial<IDocumentCardActionsStyles> = {
|
||||
root: {
|
||||
width: '100%',
|
||||
marginTop:10,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
backgroundColor: currentTheme.neutralLighter,
|
||||
borderTopWidth: 1,
|
||||
borderTopStyle: "solid",
|
||||
borderTopColor: currentTheme.neutralLight,
|
||||
},
|
||||
};
|
||||
|
||||
const buttonStylesHouver: IButtonStyles = {
|
||||
root: {
|
||||
marginRight: 10,
|
||||
},
|
||||
icon: {
|
||||
fontSize: 16,
|
||||
},
|
||||
iconHovered: {
|
||||
// color: currentTheme.themePrimary,
|
||||
fontWeight: 600,
|
||||
},
|
||||
};
|
||||
|
||||
const expandedCardStackStyle: IStackStyles = {
|
||||
root: {
|
||||
// marginTop: 10,
|
||||
paddingLeft: 25,
|
||||
paddingRight: 25,
|
||||
paddingTop: 15,
|
||||
paddingBottom: 30,
|
||||
backgroundColor: currentTheme.neutralLighterAlt,
|
||||
},
|
||||
};
|
||||
|
||||
const hoverCardStyles = mergeStyleSets({
|
||||
separatorHorizontal: mergeStyles({
|
||||
width: "100%",
|
||||
height:0,
|
||||
borderTopWidth: 1,
|
||||
borderTopStyle: "solid",
|
||||
borderTopColor: currentTheme.neutralLight,
|
||||
}),
|
||||
iconStyles: mergeStyles({
|
||||
fontSize: 16, color: currentTheme.themePrimary
|
||||
}),
|
||||
hoverHeader: mergeStyles({
|
||||
minWidth: '100%',
|
||||
borderStyle: "none",
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
return {stackCompactStyles, documentCardActionStyles, hoverCardStyles, expandedCardStackStyle, buttonStylesHouver, stackPersonaStyles };
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
export enum EOrgChartTypes {
|
||||
'SET_RENDER_MANAGERS' = 'SET_RENDER_MANAGERS',
|
||||
'SET_RENDER_DIRECT_REPORTS' = 'SET_RENDER_DIRECT_REPORTS',
|
||||
'SET_IS_LOADING' = 'SET_IS_LOADING',
|
||||
'SET_HAS_ERROR' = 'SET_HAS_ERROR',
|
||||
'SET_CURRENT_USER' = 'SET_CURRENT_USER',
|
||||
'SET_START_FROM_USER' = 'SET_START_FROM_USER'
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
import { IPropertyFieldGroupOrPerson } from '@pnp/spfx-property-controls/lib/PropertyFieldPeoplePicker';
|
||||
export interface IOrgChartProps {
|
||||
title: string;
|
||||
defaultUser: string;
|
||||
context: WebPartContext;
|
||||
startFromUser: IPropertyFieldGroupOrPerson[];
|
||||
showAllManagers: boolean;
|
||||
showActionsBar:boolean;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { IUserInfo } from "../../models/IUserInfo";
|
||||
export interface IErrorInfo {
|
||||
hasError: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
export interface IOrgChartState {
|
||||
isLoading: boolean;
|
||||
error:IErrorInfo;
|
||||
renderManagers:JSX.Element[];
|
||||
renderDirectReports: JSX.Element[];
|
||||
currentUser:IUserInfo;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.OrgChart {
|
||||
.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);
|
||||
}
|
||||
.treeContainer{
|
||||
height: 1080px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.rst__rowContents {
|
||||
border-radius: 10px;
|
||||
box-shadow: 2px -11px 46px -10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
import * as React from "react";
|
||||
import { IOrgChartProps } from "./IOrgChartProps";
|
||||
import { IOrgChartState } from "./IOrgChartState";
|
||||
import { OrgChartReducer } from "./OrgChartReducer";
|
||||
import {
|
||||
useGetUserProperties,
|
||||
manpingUserProperties,
|
||||
} from "../../hooks/useGetUserProperties";
|
||||
import { IStackStyles, Stack } from "office-ui-fabric-react/lib/Stack";
|
||||
import { PersonCard } from "../PersonCard/PersonCard";
|
||||
import { IUserInfo } from "../../models/IUserInfo";
|
||||
import { EOrgChartTypes } from "./EOrgChartTypes";
|
||||
import {
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
Overlay,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
Text,
|
||||
} from "office-ui-fabric-react";
|
||||
|
||||
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
||||
|
||||
import { getGUID } from "@pnp/common";
|
||||
import { useOrgChartStyles } from "./useOrgChartStyles";
|
||||
|
||||
const initialState: IOrgChartState = {
|
||||
isLoading: true,
|
||||
renderDirectReports: [],
|
||||
renderManagers: [],
|
||||
error: undefined,
|
||||
currentUser: undefined,
|
||||
};
|
||||
|
||||
const titleStyle: IStackStyles = {
|
||||
root: {
|
||||
paddingBottom: 40,
|
||||
},
|
||||
};
|
||||
|
||||
export const OrgChart: React.FunctionComponent<IOrgChartProps> = (
|
||||
props: IOrgChartProps
|
||||
) => {
|
||||
const { getUserProfile } = useGetUserProperties();
|
||||
const [state, dispatch] = React.useReducer(OrgChartReducer, initialState);
|
||||
const { orgChartClasses } = useOrgChartStyles();
|
||||
|
||||
const {
|
||||
renderManagers,
|
||||
renderDirectReports,
|
||||
currentUser,
|
||||
isLoading,
|
||||
error,
|
||||
}: IOrgChartState = state;
|
||||
|
||||
const {
|
||||
context,
|
||||
showAllManagers,
|
||||
startFromUser,
|
||||
showActionsBar,
|
||||
title,
|
||||
}: IOrgChartProps = props;
|
||||
|
||||
|
||||
const startFromUserId: string = React.useMemo(
|
||||
() => startFromUser && startFromUser[0].id,
|
||||
[startFromUser]
|
||||
);
|
||||
const onUserSelected = React.useCallback((selectedUser: IUserInfo) => {
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_CURRENT_USER,
|
||||
payload: selectedUser,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadOrgChart = React.useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async (selectedUser: string): Promise<any> => {
|
||||
const wRenderManagers: JSX.Element[] = [];
|
||||
const wRenderDirectReports: JSX.Element[] = [];
|
||||
|
||||
try {
|
||||
const { managersList, reportsLists } = await getUserProfile(
|
||||
selectedUser,
|
||||
startFromUserId,
|
||||
showAllManagers
|
||||
);
|
||||
if (managersList) {
|
||||
for (const managerInfo of managersList) {
|
||||
wRenderManagers.push(
|
||||
<>
|
||||
<PersonCard
|
||||
key={getGUID()}
|
||||
userInfo={managerInfo}
|
||||
onUserSelected={onUserSelected}
|
||||
selectedUser={currentUser}
|
||||
showActionsBar={showActionsBar}
|
||||
></PersonCard>
|
||||
<div
|
||||
key={getGUID()}
|
||||
className={orgChartClasses.separatorVertical}
|
||||
></div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
for (const directReport of reportsLists) {
|
||||
wRenderDirectReports.push(
|
||||
<>
|
||||
<PersonCard
|
||||
key={getGUID()}
|
||||
userInfo={directReport}
|
||||
onUserSelected={onUserSelected}
|
||||
selectedUser={currentUser}
|
||||
showActionsBar={showActionsBar}
|
||||
></PersonCard>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_HAS_ERROR,
|
||||
payload: { hasError: false, errorMessage: "" },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_IS_LOADING,
|
||||
payload: false,
|
||||
});
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_HAS_ERROR,
|
||||
payload: {
|
||||
hasError: true,
|
||||
errorMessage: "error",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return { wRenderDirectReports, wRenderManagers };
|
||||
},
|
||||
|
||||
[
|
||||
getUserProfile,
|
||||
startFromUserId,
|
||||
showAllManagers,
|
||||
onUserSelected,
|
||||
currentUser,
|
||||
showActionsBar,
|
||||
orgChartClasses.separatorVertical,
|
||||
]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
if (startFromUserId === undefined) return;
|
||||
if (startFromUserId === ''){
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_IS_LOADING,
|
||||
payload: false,
|
||||
});
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_HAS_ERROR,
|
||||
payload: {
|
||||
hasError: true,
|
||||
errorMessage: "User don't have email defined",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { currentUserProfile } = await getUserProfile(startFromUserId);
|
||||
const wCurrentUser: IUserInfo = await manpingUserProperties(
|
||||
currentUserProfile
|
||||
);
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_CURRENT_USER,
|
||||
payload: wCurrentUser,
|
||||
});
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_HAS_ERROR,
|
||||
payload: { hasError: false, errorMessage: "" },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_IS_LOADING,
|
||||
payload: false,
|
||||
});
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_HAS_ERROR,
|
||||
payload: {
|
||||
hasError: true,
|
||||
errorMessage: "error",
|
||||
},
|
||||
});
|
||||
}
|
||||
})();
|
||||
}, [getUserProfile, startFromUserId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
if (!currentUser) return;
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_IS_LOADING,
|
||||
payload: true,
|
||||
});
|
||||
|
||||
const { wRenderDirectReports, wRenderManagers } = await loadOrgChart(
|
||||
currentUser.id
|
||||
);
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_RENDER_MANAGERS,
|
||||
payload: wRenderManagers,
|
||||
});
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_RENDER_DIRECT_REPORTS,
|
||||
payload: wRenderDirectReports,
|
||||
});
|
||||
dispatch({
|
||||
type: EOrgChartTypes.SET_IS_LOADING,
|
||||
payload: false,
|
||||
});
|
||||
})();
|
||||
}, [currentUser, loadOrgChart]);
|
||||
|
||||
if (!startFromUser) {
|
||||
return (
|
||||
<Placeholder
|
||||
iconName="Edit"
|
||||
iconText="Configure your Organization Chart Web Part"
|
||||
description={"Please configure web part"}
|
||||
buttonLabel="Configure"
|
||||
onConfigure={context.propertyPane.open}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Overlay style={{ height: "100%", position: "fixed" }}>
|
||||
<Stack style={{ height: "100%" }} verticalAlign="center">
|
||||
<Spinner
|
||||
styles={{ root: { zIndex: 9999 } }}
|
||||
size={SpinnerSize.large}
|
||||
label={"loading Organization Chart..."}
|
||||
labelPosition={"bottom"}
|
||||
></Spinner>
|
||||
</Stack>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
|
||||
if (error && error.hasError) {
|
||||
return (
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="center"
|
||||
styles={{root:{padding: 20}}}
|
||||
tokens={{ childrenGap: 10 }}
|
||||
>
|
||||
<MessageBar messageBarType={MessageBarType.error} isMultiline>
|
||||
{error.errorMessage}
|
||||
</MessageBar>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack styles={{root:{padding: 20}}} >
|
||||
<Stack horizontal horizontalAlign="center" styles={titleStyle}>
|
||||
<Text variant="xLarge" block>
|
||||
{title}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack horizontalAlign="center" verticalAlign="center">
|
||||
{renderManagers}
|
||||
<PersonCard
|
||||
key={getGUID()}
|
||||
userInfo={currentUser}
|
||||
onUserSelected={onUserSelected}
|
||||
selectedUser={currentUser}
|
||||
showActionsBar={showActionsBar}
|
||||
></PersonCard>
|
||||
{renderDirectReports.length && (
|
||||
<>
|
||||
<div className={orgChartClasses.separatorVertical}></div>
|
||||
<div className={orgChartClasses.separatorHorizontal}></div>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="center"
|
||||
styles={{root:{padding: 10}}}
|
||||
tokens={{ childrenGap: 15 }}
|
||||
wrap
|
||||
>
|
||||
{renderDirectReports}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
import { IErrorInfo } from "../../components/OrgChart/IOrgChartState";
|
||||
import { IUserInfo } from "../../models/IUserInfo";
|
||||
import { EOrgChartTypes } from "./EOrgChartTypes";
|
||||
import { IOrgChartState } from "./IOrgChartState";
|
||||
export const OrgChartReducer = (
|
||||
state: IOrgChartState,
|
||||
action: { type: EOrgChartTypes; payload: unknown }
|
||||
):IOrgChartState => {
|
||||
switch (action.type) {
|
||||
case EOrgChartTypes.SET_RENDER_MANAGERS:
|
||||
return { ...state, renderManagers: action.payload as JSX.Element[] };
|
||||
case EOrgChartTypes.SET_RENDER_DIRECT_REPORTS:
|
||||
return { ...state, renderDirectReports: action.payload as JSX.Element[]};
|
||||
case EOrgChartTypes.SET_IS_LOADING:
|
||||
return { ...state, isLoading: action.payload as boolean};
|
||||
case EOrgChartTypes.SET_HAS_ERROR:
|
||||
return { ...state, error: action.payload as IErrorInfo};
|
||||
case EOrgChartTypes.SET_CURRENT_USER:
|
||||
return { ...state, currentUser: action.payload as IUserInfo};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export * from './EOrgChartTypes';
|
||||
export * from './IOrgChartProps';
|
||||
export * from './IOrgChartState';
|
||||
export * from './OrgChart.module.scss';
|
||||
export * from './OrgChart';
|
||||
export * from './OrgChartReducer';
|
|
@ -0,0 +1,33 @@
|
|||
import { mergeStyles, mergeStyleSets } from "office-ui-fabric-react";
|
||||
|
||||
const currentTheme = window.__themeState__.theme;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useOrgChartStyles = () => {
|
||||
|
||||
const orgChartClasses = mergeStyleSets({
|
||||
tilesContainer: mergeStyles({
|
||||
marginBottom: 10,
|
||||
marginTop: 0,
|
||||
gridGap: "10px",
|
||||
padding: 10,
|
||||
justifyContent: "center",
|
||||
}),
|
||||
|
||||
separatorVertical: mergeStyles({
|
||||
height: 25,
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
borderColor: currentTheme.neutralQuaternary,
|
||||
}),
|
||||
|
||||
separatorHorizontal: mergeStyles({
|
||||
width: "100%",
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
borderColor: currentTheme.neutralQuaternary,
|
||||
}),
|
||||
});
|
||||
|
||||
return { orgChartClasses };
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
import { PersonaSize } from "office-ui-fabric-react/lib/Persona";
|
||||
export interface IPersonProps {
|
||||
userEmail: string;
|
||||
text: string;
|
||||
secondaryText: string;
|
||||
tertiaryText?: string;
|
||||
pictureUrl?:string;
|
||||
size?: PersonaSize;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
IPersonaSharedProps,
|
||||
Persona,
|
||||
PersonaSize,
|
||||
} from "office-ui-fabric-react/lib/Persona";
|
||||
import { Text } from "office-ui-fabric-react/lib/Text";
|
||||
import { IPersonProps } from "./IPersonProps";
|
||||
|
||||
export const Person: React.FunctionComponent<IPersonProps> = (
|
||||
props: IPersonProps
|
||||
) => {
|
||||
const { text, secondaryText, userEmail, size, tertiaryText , pictureUrl} = props;
|
||||
|
||||
const personProps: IPersonaSharedProps = React.useMemo(() => {
|
||||
return {
|
||||
imageUrl: pictureUrl ? `/_layouts/15/userphoto.aspx?size=M&accountname=${userEmail}` : undefined,
|
||||
text: text,
|
||||
secondaryText: secondaryText,
|
||||
tertiaryText: tertiaryText,
|
||||
};
|
||||
}, [pictureUrl, userEmail, text, secondaryText, tertiaryText]);
|
||||
|
||||
const _onRenderPrimaryText = React.useCallback(() => {
|
||||
return (
|
||||
<>
|
||||
<Text
|
||||
title={text}
|
||||
variant="mediumPlus"
|
||||
block
|
||||
nowrap
|
||||
styles={{ root: { fontWeight: 600 } }}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}, [text]);
|
||||
|
||||
const _onRenderSecondaryText = React.useCallback(() => {
|
||||
return (
|
||||
<>
|
||||
<Text
|
||||
title={secondaryText}
|
||||
variant="smallPlus"
|
||||
block
|
||||
nowrap
|
||||
styles={{ root: { fontWeight: 400 } }}
|
||||
>
|
||||
{secondaryText}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}, [secondaryText]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Persona
|
||||
{...personProps}
|
||||
size={size || PersonaSize.size40}
|
||||
onRenderPrimaryText={_onRenderPrimaryText}
|
||||
onRenderSecondaryText={_onRenderSecondaryText}
|
||||
styles={{
|
||||
secondaryText: { maxWidth: 230 },
|
||||
primaryText: { maxWidth: 230 },
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
import { IUserInfo } from "../../models";
|
||||
|
||||
export interface IPersonCardProps {
|
||||
userInfo: IUserInfo;
|
||||
onUserSelected: (user: IUserInfo) => void;
|
||||
selectedUser?: IUserInfo;
|
||||
showActionsBar?: boolean;
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/* tslint:disable */
|
||||
import * as React from "react";
|
||||
import {
|
||||
DocumentCard,
|
||||
DocumentCardActions,
|
||||
DocumentCardDetails,
|
||||
IDocumentCard,
|
||||
} from "office-ui-fabric-react/lib/DocumentCard";
|
||||
|
||||
import {
|
||||
Stack,
|
||||
IButtonProps,
|
||||
HoverCard,
|
||||
HoverCardType,
|
||||
IExpandingCardProps,
|
||||
PersonaSize,
|
||||
DirectionalHint,
|
||||
} from "office-ui-fabric-react";
|
||||
import { Person } from "../Person/Person";
|
||||
import { IUserInfo } from "../../models/IUserInfo";
|
||||
import { ExpandedCard, CompactCard } from "../HoverCard";
|
||||
import { usePersonaCardStyles } from "./usePersonaCardStyles";
|
||||
import { IPersonCardProps } from "./IPersonCardProps";
|
||||
|
||||
const currentTheme = window.__themeState__.theme;
|
||||
|
||||
export const PersonCard: React.FunctionComponent<IPersonCardProps> = (
|
||||
props: IPersonCardProps
|
||||
) => {
|
||||
const { userInfo, onUserSelected, showActionsBar } = props;
|
||||
|
||||
const documentCardRef = React.useRef<IDocumentCard>(undefined);
|
||||
const {
|
||||
personaCardStyles,
|
||||
documentCardActionStyles,
|
||||
buttonStyles,
|
||||
stackPersonaStyles,
|
||||
} = usePersonaCardStyles();
|
||||
|
||||
const documentCardActions: IButtonProps[] = React.useMemo(() => {
|
||||
const cardActions: IButtonProps[] = [];
|
||||
|
||||
cardActions.push({
|
||||
iconProps: { iconName: "Chat" },
|
||||
title: "Chat",
|
||||
styles: buttonStyles,
|
||||
onClick: (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
window.open(
|
||||
`https://teams.microsoft.com/l/chat/0/0?users=${userInfo.email}&message=Hi ${userInfo.displayName} `,
|
||||
"_blank"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
if (userInfo?.email) {
|
||||
cardActions.push({
|
||||
iconProps: { iconName: "Mail" },
|
||||
title: "Mail",
|
||||
styles: buttonStyles,
|
||||
onClick: (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
window.open(`MAILTO:${userInfo.email}`, "_blank");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (userInfo?.workPhone) {
|
||||
cardActions.push({
|
||||
iconProps: { iconName: "Phone" },
|
||||
title: "Call",
|
||||
styles: buttonStyles,
|
||||
onClick: (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
window.open(`CALLTO:${userInfo.workPhone}`, "_blank");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (userInfo.hasDirectReports) {
|
||||
cardActions.push({
|
||||
iconProps: { iconName: "Org" },
|
||||
title: "View Organization",
|
||||
styles: { ...buttonStyles },
|
||||
});
|
||||
}
|
||||
return cardActions;
|
||||
}, [
|
||||
buttonStyles,
|
||||
userInfo.displayName,
|
||||
userInfo.email,
|
||||
userInfo.hasDirectReports,
|
||||
userInfo.workPhone,
|
||||
]);
|
||||
|
||||
const onRenderCompactCard = React.useCallback(
|
||||
(user: IUserInfo): JSX.Element => <CompactCard user={user} />,
|
||||
[]
|
||||
);
|
||||
|
||||
const onRenderExpandedCard = React.useCallback(
|
||||
(user: IUserInfo): JSX.Element => <ExpandedCard user={user} />,
|
||||
[]
|
||||
);
|
||||
|
||||
const expandingCardProps: IExpandingCardProps = React.useMemo(() => {
|
||||
return {
|
||||
onRenderCompactCard: onRenderCompactCard,
|
||||
onRenderExpandedCard: onRenderExpandedCard,
|
||||
renderData: userInfo,
|
||||
directionalHint: DirectionalHint.rightTopEdge,
|
||||
styles: {
|
||||
expandedCard: { backgroundColor: currentTheme.neutralLighterAlt },
|
||||
},
|
||||
gapSpace: 5,
|
||||
calloutProps: {
|
||||
isBeakVisible: false,
|
||||
},
|
||||
};
|
||||
}, [onRenderCompactCard, onRenderExpandedCard, userInfo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocumentCard
|
||||
className={personaCardStyles.tile}
|
||||
componentRef={documentCardRef}
|
||||
onClick={() => {
|
||||
// documentCardRef.current.focus();
|
||||
if (userInfo.hasDirectReports) {
|
||||
onUserSelected(userInfo);
|
||||
//
|
||||
}
|
||||
}}
|
||||
>
|
||||
<HoverCard
|
||||
expandingCardProps={expandingCardProps}
|
||||
type={HoverCardType.expanding}
|
||||
>
|
||||
<DocumentCardDetails>
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="space-between"
|
||||
styles={stackPersonaStyles}
|
||||
>
|
||||
<Person
|
||||
text={userInfo.displayName}
|
||||
secondaryText={userInfo.title}
|
||||
userEmail={userInfo.email}
|
||||
pictureUrl={userInfo.pictureUrl}
|
||||
size={PersonaSize.size40}
|
||||
/>
|
||||
</Stack>
|
||||
</DocumentCardDetails>
|
||||
</HoverCard>
|
||||
{showActionsBar && (
|
||||
<DocumentCardActions
|
||||
actions={documentCardActions}
|
||||
styles={documentCardActionStyles}
|
||||
/>
|
||||
)}
|
||||
</DocumentCard>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export * from './PersonCard';
|
||||
export * from './usePersonaCardStyles';
|
|
@ -0,0 +1,101 @@
|
|||
import { IButtonStyles, IDocumentCardActionsStyles, IStackStyles, mergeStyles, mergeStyleSets } from "office-ui-fabric-react";
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import Theme from "spfx-uifabric-themes";
|
||||
const currentTheme = window.__themeState__.theme;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const usePersonaCardStyles = () => {
|
||||
|
||||
const stackPersonaStyles: Partial<IStackStyles> = {
|
||||
root: { padding: 15 },
|
||||
};
|
||||
|
||||
const buttonStyles: IButtonStyles = {
|
||||
icon: {
|
||||
fontSize: 12,
|
||||
},
|
||||
iconHovered: {
|
||||
fontWeight: 600,
|
||||
},
|
||||
};
|
||||
|
||||
const documentCardActionStyles: Partial<IDocumentCardActionsStyles> = {
|
||||
root: {
|
||||
height: 34,
|
||||
padding: 0,
|
||||
backgroundColor: currentTheme.neutralLighterAlt,
|
||||
borderTopWidth: 1,
|
||||
borderTopStyle: "solid",
|
||||
borderTopColor: currentTheme.neutralLight,
|
||||
width: "100%",
|
||||
},
|
||||
};
|
||||
|
||||
const personaCardStyles = mergeStyleSets({
|
||||
separatorHorizontal: mergeStyles({
|
||||
width: "100%",
|
||||
borderWidth: 0.5,
|
||||
borderStyle: "solid",
|
||||
borderColor: currentTheme.neutralLight,
|
||||
}),
|
||||
iconStyles: mergeStyles({
|
||||
fontSize: 16, color: currentTheme.themePrimary
|
||||
}),
|
||||
hoverHeader: mergeStyles({
|
||||
minWidth: 260,
|
||||
maxWidth: 260,
|
||||
borderStyle: "none",
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
}),
|
||||
tileCurrentUser: mergeStyles({
|
||||
minWidth: 260,
|
||||
maxWidth: '260px !important',
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
borderRadius: 0,
|
||||
|
||||
borderColor: currentTheme.themePrimary,
|
||||
boxShadow: "0 5px 15px rgba(50, 50, 90, .1)",
|
||||
}),
|
||||
tile: mergeStyles({
|
||||
minWidth: 260,
|
||||
maxWidth: '260px !important',
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
borderRadius: 0,
|
||||
borderColor: currentTheme.neutralQuaternaryAlt,
|
||||
backgroundColor: currentTheme.white,
|
||||
boxShadow: "0 5px 15px rgba(50, 50, 90, .1)",
|
||||
selectors: {
|
||||
":hover": {
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
borderLeftStyle: "solid",
|
||||
borderRadius: 0,
|
||||
borderColor: currentTheme.themePrimary,
|
||||
// borderColor: props.color,
|
||||
// borderTopWidth: 2,
|
||||
},
|
||||
":focus": {
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
borderLeftStyle: "solid",
|
||||
borderRadius: 0,
|
||||
borderColor: currentTheme.themePrimary,
|
||||
// borderTopWidth: 2,
|
||||
},
|
||||
"@media(max-width : 480px)": {
|
||||
maxWidth: "100%",
|
||||
minWidth: "100%",
|
||||
},
|
||||
"@media((min-width : 481px) and (max-width : 12480px))": {
|
||||
maxWidth: 260,
|
||||
minWidth: "50%",
|
||||
},
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
return {documentCardActionStyles, personaCardStyles, buttonStyles, stackPersonaStyles };
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from './useGetUserProperties';
|
|
@ -0,0 +1,144 @@
|
|||
|
||||
import { sp, SPBatch} from "@pnp/sp/";
|
||||
import { IUserInfo } from "../models/IUserInfo";
|
||||
import * as React from "react";
|
||||
import { get, set } from "idb-keyval";
|
||||
import { sortBy, filter } from "lodash";
|
||||
import { IPersonProperties } from "../models/IPersonProperties";
|
||||
|
||||
/*************************************************************************************/
|
||||
// Hook to get user profile information
|
||||
// *************************************************************************************/
|
||||
|
||||
type getUserProfileFunc = ( currentUser: string,
|
||||
startUser?: string,
|
||||
showAllManagers?: boolean) => Promise<returnProfileData>;
|
||||
|
||||
type returnProfileData = { managersList:IUserInfo[], reportsLists:IUserInfo[], currentUserProfile :IPersonProperties} ;
|
||||
|
||||
export const useGetUserProperties = (): { getUserProfile:getUserProfileFunc } => {
|
||||
|
||||
const getUserProfile = React.useCallback(
|
||||
async (
|
||||
currentUser: string,
|
||||
startUser?: string,
|
||||
showAllManagers?: boolean
|
||||
): Promise<returnProfileData> => {
|
||||
if (!currentUser) return;
|
||||
const loginName = currentUser;
|
||||
const loginNameStartUser: string = startUser && startUser;
|
||||
const cacheCurrentUser:IPersonProperties = await get(`${loginName}__orgchart__`);
|
||||
let currentUserProfile:IPersonProperties = undefined;
|
||||
if (!cacheCurrentUser) {
|
||||
currentUserProfile = await sp.profiles.getPropertiesFor(loginName);
|
||||
// console.log(currentUserProfile);
|
||||
await set(`${loginName}__orgchart__`, currentUserProfile);
|
||||
} else {
|
||||
currentUserProfile = cacheCurrentUser;
|
||||
}
|
||||
// get Managers and Direct Reports
|
||||
let reportsLists: IUserInfo[] = [];
|
||||
let managersList: IUserInfo[] = [];
|
||||
|
||||
const wDirectReports: string[] =
|
||||
currentUserProfile && currentUserProfile.DirectReports;
|
||||
const wExtendedManagers: string[] =
|
||||
currentUserProfile && currentUserProfile.ExtendedManagers;
|
||||
|
||||
// Get Direct Reports if exists
|
||||
if (wDirectReports && wDirectReports.length > 0) {
|
||||
reportsLists = await getDirectReports(wDirectReports);
|
||||
}
|
||||
// Get Managers if exists
|
||||
if (startUser && wExtendedManagers && wExtendedManagers.length > 0) {
|
||||
managersList = await getExtendedManagers(
|
||||
wExtendedManagers,
|
||||
loginNameStartUser,
|
||||
showAllManagers
|
||||
);
|
||||
}
|
||||
|
||||
return { managersList, reportsLists, currentUserProfile } ;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return { getUserProfile } ;
|
||||
};
|
||||
|
||||
const getDirectReports = async (
|
||||
directReports: string[]
|
||||
): Promise<IUserInfo[]> => {
|
||||
const _reportsList: IUserInfo[] = [];
|
||||
const batch: SPBatch = sp.createBatch();
|
||||
for (const userReport of directReports) {
|
||||
const cacheDirectReport: IPersonProperties = await get(`${userReport}__orgchart__`);
|
||||
if (!cacheDirectReport) {
|
||||
sp.profiles
|
||||
.inBatch(batch)
|
||||
.getPropertiesFor(userReport)
|
||||
.then(async (directReport: IPersonProperties) => {
|
||||
_reportsList.push(await manpingUserProperties(directReport));
|
||||
await set(`${userReport}__orgchart__`, directReport);
|
||||
});
|
||||
} else {
|
||||
_reportsList.push(await manpingUserProperties(cacheDirectReport));
|
||||
}
|
||||
}
|
||||
await batch.execute();
|
||||
return sortBy(_reportsList, ["displayName"]);
|
||||
};
|
||||
|
||||
const getExtendedManagers = async (
|
||||
extendedManagers: string[],
|
||||
startUser: string,
|
||||
showAllManagers: boolean
|
||||
): Promise<IUserInfo[]> => {
|
||||
const wManagers: IUserInfo[] = [];
|
||||
const batch: SPBatch = sp.createBatch();
|
||||
|
||||
for (const manager of extendedManagers) {
|
||||
if (!showAllManagers && manager !== startUser) {
|
||||
continue;
|
||||
}
|
||||
const cacheManager: IPersonProperties = await get(`${manager}__orgchart__`);
|
||||
if (!cacheManager) {
|
||||
sp.profiles
|
||||
.inBatch(batch)
|
||||
.getPropertiesFor(manager)
|
||||
.then(async (_profile: IPersonProperties) => {
|
||||
wManagers.push(await manpingUserProperties(_profile));
|
||||
await set(`${manager}__orgchart__`, _profile);
|
||||
});
|
||||
} else {
|
||||
wManagers.push(await manpingUserProperties(cacheManager));
|
||||
}
|
||||
}
|
||||
await batch.execute();
|
||||
return wManagers;
|
||||
};
|
||||
|
||||
export const manpingUserProperties = async (
|
||||
userProperties: IPersonProperties
|
||||
): Promise<IUserInfo> => {
|
||||
|
||||
return {
|
||||
displayName: userProperties.DisplayName as string,
|
||||
email: userProperties.Email as string,
|
||||
title: userProperties.Title as string,
|
||||
pictureUrl: userProperties.PictureUrl,
|
||||
id: userProperties.AccountName,
|
||||
userUrl: userProperties.UserUrl,
|
||||
numberDirectReports: userProperties.DirectReports.length,
|
||||
hasDirectReports: userProperties.DirectReports.length > 0 ? true : false,
|
||||
hasPeers: userProperties.Peers.length > 0 ? true : false,
|
||||
numberPeers: userProperties.Peers.length,
|
||||
department: filter(userProperties?.UserProfileProperties,{"Key": "Department"})[0].Value ?? '',
|
||||
workPhone: filter(userProperties?.UserProfileProperties,{"Key": "WorkPhone"})[0].Value ?? '',
|
||||
cellPhone: filter(userProperties?.UserProfileProperties,{"Key": "CellPhone"})[0].Value ?? '',
|
||||
location: filter(userProperties?.UserProfileProperties,{"Key": "SPS-Location"})[0].Value ?? '',
|
||||
office: filter(userProperties?.UserProfileProperties,{"Key": "Office"})[0].Value ?? '',
|
||||
manager: filter(userProperties?.UserProfileProperties,{"Key": "Manager"})[0].Value ?? '',
|
||||
loginName: userProperties.loginName
|
||||
};
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,18 @@
|
|||
export interface IPersonProperties {
|
||||
AccountName: string;
|
||||
DirectReports: string[];
|
||||
DisplayName: string;
|
||||
Email: string;
|
||||
ExtendedManagers: string[];
|
||||
ExtendedReports: string[];
|
||||
IsFollowed: boolean;
|
||||
LatestPost: string;
|
||||
Peers: string[];
|
||||
PersonalSiteHostUrl: string;
|
||||
PersonalUrl: string;
|
||||
PictureUrl: string;
|
||||
Title: string;
|
||||
UserProfileProperties: { Key: string; Value: string; ValueType: string }[];
|
||||
UserUrl: string;
|
||||
loginName: string;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export interface IUser {
|
||||
displayName: string;
|
||||
email: string;
|
||||
isAnonymousGuestUser?: boolean;
|
||||
isExternalGuestUser?: boolean;
|
||||
loginName?: string;
|
||||
preferUserTimeZone?: boolean;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
/* tslint:disable */
|
||||
import { IUser } from "./IUser";
|
||||
export interface IUserInfo extends IUser {
|
||||
title:string;
|
||||
pictureUrl?: string;
|
||||
userUrl?:string;
|
||||
id?:string;
|
||||
hasDirectReports?:boolean;
|
||||
numberDirectReports?:number;
|
||||
hasPeers?:boolean;
|
||||
numberPeers?:number;
|
||||
manager?:string;
|
||||
department?:string;
|
||||
workPhone?:string;
|
||||
cellPhone?:string;
|
||||
location?:string;
|
||||
office?: string;
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './IPersonProperties';
|
||||
export * from './IUser';
|
||||
export * from './IUserInfo';
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "0338da15-07f9-4a53-b267-d790fb495cca",
|
||||
"alias": "OrganizationChartWebPart",
|
||||
"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,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "SPFx - Custom WebParts" },
|
||||
"title": { "default": "Organization Chart" },
|
||||
"description": { "default": "Show Company Organization Chart" },
|
||||
"officeFabricIconFontName": "Org",
|
||||
"properties": {
|
||||
"title": "Organization Chart",
|
||||
"showAllManagers": true,
|
||||
"showActionsBar": true
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import {
|
||||
PropertyFieldPeoplePicker,
|
||||
PrincipalType,
|
||||
IPropertyFieldGroupOrPerson,
|
||||
} from "@pnp/spfx-property-controls/lib/PropertyFieldPeoplePicker";
|
||||
import * as strings from 'OrganizationChartWebPartStrings';
|
||||
import {OrgChart} from '../../components/OrgChart/OrgChart';
|
||||
|
||||
import { IOrgChartProps } from "../../components/OrgChart/IOrgChartProps";
|
||||
import { sp } from "@pnp/sp";
|
||||
export interface IOrganizationChartWebPartProps {
|
||||
title: string;
|
||||
currentUser: string;
|
||||
selectedUser: IPropertyFieldGroupOrPerson[];
|
||||
showAllManagers: boolean;
|
||||
showActionsBar: boolean;
|
||||
}
|
||||
|
||||
export default class OrganizationChartWebPart extends BaseClientSideWebPart<IOrganizationChartWebPartProps> {
|
||||
public async onInit(): Promise<void> {
|
||||
sp.setup({
|
||||
spfxContext: this.context,
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IOrgChartProps > = React.createElement(
|
||||
OrgChart,
|
||||
{
|
||||
title: this.properties.title,
|
||||
defaultUser: this.properties.currentUser,
|
||||
startFromUser: this.properties.selectedUser,
|
||||
showAllManagers: this.properties.showAllManagers,
|
||||
context: this.context,
|
||||
showActionsBar: this.properties.showActionsBar
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
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("title", {
|
||||
label: strings.TitleFieldLabel,
|
||||
}),
|
||||
PropertyFieldPeoplePicker("selectedUser", {
|
||||
context: this.context,
|
||||
label: strings.startFromUserLabel,
|
||||
initialData: this.properties.selectedUser,
|
||||
key: "peopleFieldId",
|
||||
multiSelect: false,
|
||||
allowDuplicate: false,
|
||||
principalType: [PrincipalType.Users],
|
||||
onPropertyChange: this.onPropertyPaneFieldChanged,
|
||||
properties: this.properties,
|
||||
onGetErrorMessage: null,
|
||||
}),
|
||||
PropertyPaneToggle("showAllManagers", {
|
||||
label: strings.showAllManagers,
|
||||
}),
|
||||
PropertyPaneToggle("showActionsBar", {
|
||||
label: strings.showactionsLabel,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
}
|
10
samples/react-organization-chart/src/webparts/organizationChart/loc/en-us.js
vendored
Normal file
10
samples/react-organization-chart/src/webparts/organizationChart/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Company Organization Chart",
|
||||
"BasicGroupName": "Properties",
|
||||
"TitleFieldLabel": "Title",
|
||||
"startFromUserLabel": "Start from user",
|
||||
"showactionsLabel": "Show actions bar",
|
||||
"showAllManagers": "Show all managers"
|
||||
}
|
||||
});
|
13
samples/react-organization-chart/src/webparts/organizationChart/loc/mystrings.d.ts
vendored
Normal file
13
samples/react-organization-chart/src/webparts/organizationChart/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
declare interface IOrganizationChartWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
TitleFieldLabel: string;
|
||||
startFromUserLabel: string;
|
||||
showactionsLabel:string;
|
||||
showAllManagers:string;
|
||||
}
|
||||
|
||||
declare module 'OrganizationChartWebPartStrings' {
|
||||
const strings: IOrganizationChartWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"es6",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.promise"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue