initial pr for react-groups-teams

initial pull request for react groups & teams web part
This commit is contained in:
ReactIntern 2021-05-06 00:20:20 -04:00
parent c045ace864
commit 54d0c6ecb3
31 changed files with 19370 additions and 0 deletions

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

32
samples/react-groups-teams/.gitignore vendored Normal file
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": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.9.1",
"libraryName": "react-groups",
"libraryId": "e562b13d-7e90-4a1e-810c-ccde0ff27866",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,26 @@
## react-groups
This is where you include your WebPart documentation.
### Building the code
```bash
git clone the repo
npm i
npm i -g gulp
gulp
```
This package produces the following:
* lib/* - intermediate-stage commonjs build artifacts
* dist/* - the bundled script, along with other resources
* deploy/* - all resources which should be uploaded to a CDN.
### Build options
gulp clean - TODO
gulp test - TODO
gulp serve - TODO
gulp bundle - TODO
gulp package-solution - TODO

View File

@ -0,0 +1,101 @@
## All Microsoft 365 Groups and Teams with SPFx
### Summary
Web part pulls all Microsoft 365 Groups and Teams that the logged in user has access to view.
1. The Microsoft Groups view has filter option for private or public groups and can switch between viewing all groups or just my groups.
- Group Name (hover for group description)
- Link to email
- Link to SharePoint site
- Link to calendar
- Link to Planner plan (if available)
- Group privacy
2. The Microsoft Teams view has filter option for private or public Teams.
- Team Name (hover for group description)
- Link to email
- Link to SharePoint site
- Link to calendar
- Link to Planner plan (if available)
- Link to Team
- Team privacy
Each Team o Uses SharePoint theme.
![picture of the web part in action](./assets/Groups-in-my-organization.png)
![picture of the web part in action](./assets/My-Teams-Teams-Side-By-Side-Theme.png)
![picture of the web part in action](./assets/My-Groups-Public-Filter.png)
![picture of the web part in action](./assets/My-Teams-Teams-With-Tooltip.png)
## Compatibility
![SPFx 1.10](https://img.shields.io/badge/SPFx-1.10.0-green.svg)
![Node.js LTS 8.x | LTS 10.x](https://img.shields.io/badge/Node.js-LTS%208.x%20%7C%20LTS%210.x-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
![Workbench Hosted: Only after API permissions granted](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Only after API permissions granted")
## Applies to
* [SharePoint Framework Developer Preview](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
## Solution
Solution|Author(s)
--------|---------
MicrosoftGroups | [Allie](https://github.com/ReactIntern) |
## Version history
| Version | Date | Comments |
| ------- | ---------------- | --------------- |
| 1.0.0 | April 16, 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.**
---
# Prerequisites
- Administrative access to Azure AD of Microsoft 365 tenant
- SharePoint Online tenant
- You need following set of permissions in order to manage Microsoft 365 Groups and Teams
```
"webApiPermissionRequests": [{
"resource": "Microsoft Graph",
"scope": "Groups.Read.All"
}, {
"resource": "Team",
"scope": "Teams.ReadBasic.All"
}]
```
# Minimal Path to Awesome
- Clone this repo
- Navigate to the folder with current sample
- Restore dependencies: `$ npm i`
- Bundle the solution: `$ gulp bundle --ship`
- Package the solution: `$ gulp package-solution --ship`
- Upload to SharePoint tenant app catalog
- You will see a message saying that solution has pending permissions which need to be approved
- Approve the permission requests.
- Run `$ gulp serve --nobrowser`
- Open hosted workbench, i.e. `https://<tenant>.sharepoint.com/sites/<your site>/_layouts/15/workbench.aspx`
- Search and add `O365 Groups Manager` web part to see it in action
# Features
This project contains sample client-side web part built on the SharePoint Framework illustrating possibilities to quickly gain access to features in Microsoft 365 Groups and Teams using React and MS Graph.
This sample illustrates the following concepts on top of the SharePoint Framework:
- Explore MS Graph APIs for Microsoft 365 Group
- Using the MSGraphClient in a SharePoint Framework web part
- Requesting API permissions in a SharePoint Framework package
- Communicating with the Microsoft Graph using its REST API
- Using Office UI Fabric controls for building SharePoint Framework client-side web parts
- Passing web part properties to React components
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-groups-demo" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"microsoft-groups-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/microsoftGroups/MicrosoftGroupsWebPart.js",
"manifest": "./src/webparts/microsoftGroups/MicrosoftGroupsWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"MicrosoftGroupsWebPartStrings": "lib/webparts/microsoftGroups/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-groups",
"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-groups-client-side-solution",
"id": "e562b13d-7e90-4a1e-810c-ccde0ff27866",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"webApiPermissionRequests":[{
"resource": "Microsoft Graph",
"scope": "Groups.Read.All"
}, {
"resource": "Team",
"scope": "Teams.ReadBasic.All"
}]
},
"paths": {
"zippedPackage": "solution/react-groups.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);

17989
samples/react-groups-teams/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
{
"name": "react-groups",
"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",
"@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 @@
// 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": "5ced32db-af85-469a-a3cb-39f3986e1f1a",
"alias": "MicrosoftGroupsWebPart",
"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": "Microsoft Groups" },
"description": { "default": "Microsoft Groups description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "Microsoft Groups"
}
}]
}

View File

@ -0,0 +1,59 @@
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 'MicrosoftGroupsWebPartStrings';
import MicrosoftGroups from './components/MicrosoftGroups';
export interface IMicrosoftGroupsWebPartProps {
description: string;
}
export default class MicrosoftGroupsWebPart extends BaseClientSideWebPart<IMicrosoftGroupsWebPartProps> {
public render(): void {
const element: React.ReactElement<IMicrosoftGroupsWebPartProps > = React.createElement(
MicrosoftGroups,
{
context: this.context
}
);
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('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,240 @@
import * as React from 'react';
import { MSGraphClient } from "@microsoft/sp-http";
import { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './MicrosoftGroups.module.scss';
import { Modal, TooltipHostBase } from 'office-ui-fabric-react';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { iconClass } from './MyTeams';
export interface IGraphConsumerProps {
context: WebPartContext;
}
export interface IUserItem {
Topic: string;
DeliveryDate: Date;
}
export interface IGraphConsumerState {
AllGroupsresults: any;
MyGroupResults: any;
plannerId: string;
either: string;
Allgroupsdisplay: number;
mygroupsdisplay: number;
mode: string;
title: string;
isOpen: boolean;
MoreDetails: any;
Name: string;
Description: string;
TenantUrl: string;
MyGroupResultsFiltered: any;
AllGroupsresultsFiltered: any;
}
export default class MicrosoftGroups extends React.Component<IGraphConsumerProps, IGraphConsumerState> {
public _plannerIDs = [];
public _arr = [];
private graphClient: MSGraphClient = null;
public Tenant = this.props.context.pageContext.web.absoluteUrl.split('.')[0].split('//')[1];
constructor(props) {
super(props);
this.GetPlanner = this.GetPlanner.bind(this);
this.GetGroups = this.GetGroups.bind(this);
this.state = {
AllGroupsresults: [],
MyGroupResults: [],
plannerId: '',
either: '',
Allgroupsdisplay: 0,
mygroupsdisplay: 0,
mode: 'All',
title: 'Groups In My Organization',
isOpen: false,
MoreDetails: [],
Name: '',
Description: '',
TenantUrl: '',
MyGroupResultsFiltered: [],
AllGroupsresultsFiltered: []
};
}
public SwitchGroupList() {
if (this.state.title === 'Groups In My Organization') {
this.setState({ title: "My Groups" });
}
else {
this.setState({ title: 'Groups In My Organization' });
}
}
public SwitchGroupList2(Switch) {
if (Switch === 'All') {
this.setState({ AllGroupsresultsFiltered: this.state.AllGroupsresults });
this.setState({ MyGroupResultsFiltered: this.state.MyGroupResults });
}
else {
const SwitchedALL = this.state.AllGroupsresults.filter(item => item.Visibility === Switch);
this.setState({ AllGroupsresultsFiltered: SwitchedALL });
const SwitchedMY = this.state.MyGroupResults.filter(item => item.Visibility === Switch);
this.setState({ MyGroupResultsFiltered: SwitchedMY });
}
this.setState({ mode: Switch });
}
public OpenModal(GroupInfo) {
var array = [];
array.push(GroupInfo);
this.setState({ isOpen: true, MoreDetails: array, Name: GroupInfo.Name, Description: GroupInfo.Description });
}
public async GetPlanner() {
this.state.MyGroupResults.map(async GroupId => {
try {
this.graphClient = await this.props.context.msGraphClientFactory.getClient();
const results: any = await this.graphClient
.api(`/groups/${GroupId.Id}/planner/plans`)
.version('v1.0')
.get();
if (results.value.length > 0) {
var ID; results.value.map(Items => {
ID = Items.id;
});
var URL = `https://tasks.office.com/${this.Tenant}.com/EN-US/Home/Planner#/plantaskboard?groupId=${GroupId.Id}&planId=${ID}`;
var Planner = { Planner: URL };
var Results = Object.assign(GroupId, Planner);
GroupId = Results;
this.state.AllGroupsresults.map(Group => {
if (Group.Name === GroupId.Name) {
var Results2 = Object.assign(Group, Planner);
Group = Results2;
}
});
}
return this.setState({ MyGroupResultsFiltered: this.state.MyGroupResults });
} catch (error) {
}
});
}
public GetGroups() {
var
GroupIDListAll = [],
GroupIDListMy = [],
myarray = [];
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`me/transitiveMemberOf/microsoft.graph.group?$filter=groupTypes/any(a:a eq 'unified')`)
.version("v1.0")
.get((err, res) => {
if (err) {
}
if (res) {
res.value.map((item) => {
myarray.push({ Name: item.displayName, Id: item.id, Description: item.description, Mail: item.mail, Visibility: item.visibility });
GroupIDListMy.push({ Id: item.id });
});
this.setState({ MyGroupResultsFiltered: myarray, MyGroupResults: myarray });
this.GetPlanner();
}
});
});
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`groups?$filter=groupTypes/any(a:a eq 'unified')`)
.version("v1.0")
.get((err, res) => {
if (err) {
};
if (res) {
res.value.map((item) => {
this._arr.push({ Name: item.displayName, Id: item.id, Description: item.description, Mail: item.mail, Visibility: item.visibility });
GroupIDListAll.push([item.id]);
});
this.setState({ AllGroupsresults: this._arr, AllGroupsresultsFiltered: this._arr });
this.GetPlanner();
}
});
});
}
public componentDidMount() {
this.GetGroups();
}
public render(): React.ReactElement<IGraphConsumerProps> {
var Replaceregex = /\s+/g;
return <div className={styles.test}>
<div className={styles.tableCaptionStyle}>{this.state.title}
<div>
{this.state.mode === 'Public' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Public')}>Public</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Public')}>Public</button>}
{this.state.mode === 'All' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('All')}>All</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('All')}>All</button>}
{this.state.mode === 'Private' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Private')}>Private</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Private')}>Private</button>}
</div>
<button className={styles.SwitchGroups} onClick={() => this.SwitchGroupList()}> View {this.state.title === 'My Groups' ?
'Groups in my Organization' : 'My Groups'}</button>
</div>
<div className={styles.tableStyle}>
<div className={styles.headerStyle}>
<div className={styles.Center}>Group</div>
<div className={styles.Center}>Mail</div>
<div className={styles.Center}>Site</div>
<div className={styles.Center}>Calendar</div>
<div className={styles.Center}>Planner</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>Visibility</div>
</div>
{this.state.title === 'My Groups' ? this.state.MyGroupResultsFiltered.map(Group => {
var GroupEmailSplit = Group.Mail.split("@");
Group.Mail = GroupEmailSplit[0];
return <div className={styles.rowStyle}>
<div className={styles.ToolTipName}>{Group.Name}<span className={styles.ToolTip}>{Group.Description}</span></div>
<a className={styles.Center} href={`https://outlook.office365.com/mail/group/${this.Tenant}.com/${Group.Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon>
</a>
<a className={styles.Center} href={`https://${this.Tenant}.sharepoint.com/sites/${Group.Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office365.com/calendar/group/${this.Tenant}.com/${Group.Name.replace(Replaceregex, '')}/view/week`}>
<Icon className={iconClass} style={{ color: '#119AE2' }} iconName="Calendar"></Icon>
</a>
<div className={styles.Center}>
{Group.Planner === undefined ? <div></div> : <a href={Group.Planner}>
<Icon className={iconClass} style={{ color: '#077D3F' }} iconName="ViewListTree"></Icon></a>}
</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>{Group.Visibility}</div>
</div>
}) : this.state.AllGroupsresultsFiltered.map(Group => {
var GroupEmailSplit = Group.Mail.split("@");
Group.Mail = GroupEmailSplit[0];
return <div className={styles.rowStyle}>
<div className={styles.ToolTipName}>{Group.Name}<span className={styles.ToolTip}>{Group.Description}</span></div>
<a className={styles.Center} href={`https://outlook.office365.com/mail/group/${this.Tenant}.com/${Group.Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon>
</a>
<a className={styles.Center} href={`https://${this.Tenant}.sharepoint.com/sites/${Group.Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office365.com/calendar/group/${this.Tenant}.com/${Group.Name.replace(Replaceregex, '')}/view/week`}>
<Icon className={iconClass} style={{ color: '#119AE2' }} iconName="Calendar"></Icon>
</a>
<div className={styles.Center}>
{Group.Planner === undefined ? <div></div> : <a href={Group.Planner}>
<Icon className={iconClass} style={{ color: '#077D3F' }} iconName="ViewListTree"></Icon></a>}
</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>{Group.Visibility}</div>
</div>
})}
</div>
</div>
}
}

View File

@ -0,0 +1,425 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.test {
text-align: center;
vertical-align: middle;
}
.none {
display:none;
}
.Center {
padding-top: 10px;
padding-bottom: 10px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
padding-right: 13px;
padding-left: 13px;
border-bottom: 2px solid white;
/*
position: relative;
padding-bottom: 0px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
padding: 8px 13px 8px;
border-bottom: 2px solid white;
max-width: 132px;
word-break: break-word;*/
}
.SwitchGroups{
position:absolute;
bottom:2px;
right:10px;
font-size:16px;
background-color: transparent;
border: none;
cursor:pointer;
color:white;
}
.SwitchGroups:hover{
text-decoration: underline;
}
.headerStyle{
background-color: "[theme: themePrimary, default: #0078d7]";
display: table-row ;
border: solid ;
text-align : center ;
width : 100px ;
height : 30px ;
padding-top : 10px ;
color : white ;
}
.tableStyle{
display: table;
min-width: 100%;
margin-right: auto;
margin-left: auto;
}
.panelStyle{
background: lightblue ;
}
.tableCaptionStyle{
background-color: "[theme: themePrimary, default: #0078d7]";
font-size: 20px ;
font-weight: bold ;
border: solid ;
text-align:center ;
height:79px;
padding-top:3px ;
border-radius:25px ;
color:white;
border-radius: 25px;
margin-left: auto;
margin-top: auto;
min-width: 100%;
text-align: center;
vertical-align: middle;
position: relative;
font-weight:normal;
}
.rowStyle{
display : table-row ;
background: #eee ;
padding: 20px ;
margin: 20px ;
font-weight : bold ;
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.CellStyle{
display: table-cell ;
border: solid ;
border-color : white ;
text-align : center ;
min-width : 75px ;
height : 30px ;
width: fit-content;
padding-top : 10px ;
}
.buttons {
display: table-cell ;
text-align : center ;
width : 160px ;
height : 30px ;
padding-top : 10px ;
}
.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;
}
}
// @import '~office-ui-fabric-react/dist/sass/References.scss';
.test {
text-align: center;
vertical-align: middle;
}
.none {
display:none;
}
.CenterRight {
border-right:none;
padding: 5px 10px 3px;
margin-left: 26px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
//border-bottom: 2px solid white;
max-width: 113px;
word-break: break-word;
}
.Center {
padding: 5px 10px 3px;
margin-left: 26px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
border-bottom: 2px solid white;
max-width: 113px;
word-break: break-word;
}
.ToolTipName {
position: relative;
padding-bottom: 0px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
padding: 8px 13px 8px;
border-bottom: 2px solid white;
max-width: 132px;
word-break: break-word;
}
.ToolTip {
visibility: hidden;
padding: 7px;
background-color: $ms-color-themeDark;
color: #fff;
text-align: center;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 86%;
right: 0%;
left: 0%;
padding-left: 7px;
padding-right: 7px;
}
.ToolTipName:hover .ToolTip {
visibility: visible;
padding: 5px 0px 5px 0px;
background-color: "[theme: themeLight, default: #0078d7]";
}
.MainViewCenter {
width: 50%;
padding-top: 10px;
padding-bottom: 10px;
display: table-cell;
vertical-align: middle;
text-align: center;
padding-right: 13px;
padding-left: 13px;
cursor:pointer;
background-color: "[theme: themeDark, default: #0078d7]";
border: none;
color: white;
border-right: 2px solid white;
}
.MainViewCenter :hover{
text-decoration: underline;
}
.Filters {
cursor: pointer;
background-color: "[theme: themePrimary, default: #0078d7]";
color: #fff;
border: none;
border: 2px solid;
border-color: "[theme: themeDark, default: #0078d7]";
margin-right: 4px;
margin-top: 9px;
}
.Filters:focus {
outline: none;
}
.SelectedFilter {
cursor: pointer;
background-color: "[theme: themeDark, default: #0078d7]";
color: #fff;
border: none;
border: 2px solid;
border-color: "[theme: themeDark, default: #0078d7]";
margin-right: 4px;
margin-top: 9px;}
.SelectedFilter:focus{
outline:none;
}
.OneNoteIcon {
height: 32px;
width: 32px;
cursor: pointer;
}
.SwitchGroups{
position:absolute;
bottom:2px;
right:10px;
font-size:16px;
background-color: transparent;
border: none;
cursor:pointer;
color:white;
}
.SwitchGroups:hover{
text-decoration: underline;
}
.headerStyle{
background-color: "[theme: themeSecondary, default: #0078d7]"; //themeSecondary
display: table-row ;
border: solid ;
text-align : center ;
width : 100px ;
height : 30px ;
padding-top : 10px ;
color : white ;
}
.tableStyle{
display: table;
min-width: 100%;
margin-right: auto;
margin-left: auto;
}
.panelStyle{
background: lightblue ;
}
.tableCaptionStyle{
background-color: "[theme: themePrimary, default: #0078d7]";
// display: block ;
font-size: 20px ;
font-weight: bold ;
border: solid ;
text-align:center ;
height:79px;
padding-top:3px ;
border-radius:25px ;
color:white;
border-radius: 25px;
margin-left: auto;
margin-top: auto;
min-width: 100%;
text-align: center;
vertical-align: middle;
position: relative;
font-weight:normal;
}
.rowStyle{
display : table-row ;
background: #eee ;
padding: 20px ;
margin: 20px ;
font-weight : bold ;
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.CellStyle{
display: table-cell ;
border: solid ;
border-color : white ;
text-align : center ;
min-width : 75px ;
height : 30px ;
width: fit-content;
padding-top : 10px ;
}
.buttons {
display: table-cell ;
text-align : center ;
width : 160px ;
height : 30px ;
padding-top : 10px ;
}
.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,36 @@
import * as React from 'react';
import { MSGraphClient } from "@microsoft/sp-http";
import { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './MicrosoftGroups.module.scss';
import TeamsGraphConsumer from './MyTeams';
import GraphConsumer from './Microsoft365Groups';
export interface IMicrosoftGroupsProps {
context: WebPartContext;
}
export interface IMicrosoftGroupsState {
title: string;
}
export default class MicrosoftGroups extends React.Component<IMicrosoftGroupsProps, IMicrosoftGroupsState> {
constructor(props) {
super(props);
this.state = {
title: ''
};
}
public SwitchGroupList(ID) {
this.setState({title: ID});
}
public render(): React.ReactElement<IMicrosoftGroupsProps> {
return <div>
<div className={styles.tableStyle}>
<div className={styles.headerStyle} >
<button className={styles.MainViewCenter} id={'Microsoft 365 Groups'} onClick={() => this.SwitchGroupList('Microsoft 365 Groups')}>Microsoft 365 Groups</button>
<button className={styles.MainViewCenter} id={'My Teams'} onClick={() => this.SwitchGroupList('My Teams')}>My Teams</button>
</div></div>
{this.state.title === 'Microsoft 365 Groups' ? <GraphConsumer context={this.props.context}/> : <div></div>}
{this.state.title === 'My Teams' ? <TeamsGraphConsumer context={this.props.context} hidden={false}/>: <TeamsGraphConsumer context={this.props.context} hidden={true}/>}
</div>
}
}

View File

@ -0,0 +1,200 @@
import * as React from 'react';
import { MSGraphClient } from "@microsoft/sp-http";
import { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './MicrosoftGroups.module.scss';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
export const iconClass = mergeStyles({
fontSize: 32,
height: 32,
width: 32
});
export interface IMyTeamsProps {
context: WebPartContext;
hidden: Boolean;
}
export interface IUserItem {
Topic: string;
DeliveryDate: Date;
}
export interface IMyTeamsState {
MyGroupResults: any;
MyGroupsresultsFiltered: any;
plannerId: string;
Allgroupsdisplay: number;
mygroupsdisplay: number;
mode: string;
title: string;
TenantURL: String;
}
export default class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
public Tenant = this.props.context.pageContext.web.absoluteUrl.split('.')[0].split('//')[1];
private graphClient: MSGraphClient = null;
constructor(props) {
super(props);
this.state = {
MyGroupResults: [],
MyGroupsresultsFiltered: [],
plannerId: '',
Allgroupsdisplay: 0,
mygroupsdisplay: 0,
mode: 'All',
title: 'Teams in Microsoft Teams In My Organization',
TenantURL: ''
};
}
public SwitchGroupList(Switch) {
if (Switch === 'All') {
this.setState({ MyGroupsresultsFiltered: this.state.MyGroupResults });
}
else {
const SwitchedMY = this.state.MyGroupResults.filter(item => item.Visibility === Switch);
this.setState({ MyGroupsresultsFiltered: SwitchedMY });
}
this.setState({ mode: Switch });
}
public async GetPlanner() {
this.state.MyGroupResults.map(async GroupId => {
try {
this.graphClient = await this.props.context.msGraphClientFactory.getClient();
const results: any = await this.graphClient
.api(`/groups/${GroupId.Id}/planner/plans`)
.version('v1.0')
.get();
if (results.value.length > 0) {
var ID; results.value.map(Items => {
ID = Items.id;
});
var URL = `https://tasks.office.com/${this.Tenant}.com/EN-US/Home/Planner#/plantaskboard?groupId=${GroupId.Id}&planId=${ID}`;
var Planner = { Planner: URL };
var Results = Object.assign(GroupId, Planner);
GroupId = Results;
}
return results.value;
} catch (error) {
}
});
}
public GetTeamsURL() {
this.state.MyGroupResults.map(Group => {
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`/teams/${Group.Id}/?$select=webUrl`)
.version("v1.0")
.get((err, res) => {
if (err) {
return;
}
if (res) {
var Results = Object.assign(Group, { WebUrl: res.webUrl });
Group = Results;
this.setState({ MyGroupsresultsFiltered: this.state.MyGroupResults });
}
});
});
});
}
public async GetMail() {
this.state.MyGroupResults.map(Group => {
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`groups/${Group.Id}/`)
.version("v1.0")
.get((err, res) => {
if (err) {
return;
}
if (res) {
var Results = Object.assign(Group, { Mail: res.mail });
Group = Results;
}
});
});
});
}
public componentDidMount() {
this.setState({ TenantURL: this.props.context.pageContext.web.absoluteUrl });
var array = [];
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`me/joinedTeams`)
.version("v1.0")
.get((err, res) => {
if (err) {
return;
}
if (res) {
res.value.map((item) => {
array.push({ Name: item.displayName, Id: item.id, Description: item.description, Visibility: item.visibility });
});
this.setState({ MyGroupResults: array });
this.GetMail();
this.GetPlanner();
this.GetTeamsURL();
}
});
});
}
public render(): React.ReactElement<IMyTeamsProps> {
var Replaceregex = /\s+/g;
return this.props.hidden ? <div></div> : <div className={styles.test}>
<div className={styles.tableCaptionStyle} style={{ borderRight: 'none' }}>My Teams Teams<div>
{this.state.mode === 'Public' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList('Public')}>Public</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList('Public')}>Public</button>}
{this.state.mode === 'All' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList('All')}>All</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList('All')}>All</button>}
{this.state.mode === 'Private' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList('Private')}>Private</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList('Private')}>Private</button>}</div>
</div>
<div className={styles.tableStyle}>
<div className={styles.headerStyle}>
<div className={styles.Center}>Team</div>
<div className={styles.Center}>Mail</div>
<div className={styles.Center}>Site</div>
<div className={styles.Center}>Calendar</div>
<div className={styles.Center}>Planner</div>
<div className={styles.Center}>WebUrl</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>Visibility</div>
</div>
{this.state.MyGroupsresultsFiltered.map(Team => {
Team.Visibility = Team.Visibility.substr(0, 1).toUpperCase() + Team.Visibility.substr(1);
var GroupEmailSplit = Team.Mail.split("@");
var Mail = GroupEmailSplit[0];
return (
<div className={styles.rowStyle}>
<div className={styles.ToolTipName}>{Team.Name}<span className={styles.ToolTip}>{Team.Description}</span></div>
<a className={styles.Center} href={`https://outlook.office365.com/mail/group/${this.Tenant}.com/${Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon></a>
<a className={styles.Center} href={`https://${this.Tenant}.sharepoint.com/sites/${Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office365.com/calendar/group/${this.Tenant}.com/${Team.Name.replace(Replaceregex, '')}/view/week`}>
<Icon className={iconClass} style={{ color: '#119AE2' }} iconName="Calendar"></Icon>
</a>
<div className={styles.Center}>
{Team.Planner === undefined ? <div></div> : <a href={Team.Planner}>
<Icon className={iconClass} style={{ color: '#077D3F' }} iconName="ViewListTree"></Icon>
</a>}
</div>
<a className={styles.Center} href={`${Team.WebUrl}`}>
<Icon className={iconClass} style={{ color: '#424AB5' }} iconName="TeamsLogo"></Icon>
</a>
<div className={styles.Center} style={{ borderRight: 'none' }}>{Team.Visibility}</div>
</div>
);
})}</div></div>
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface IMicrosoftGroupsWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'MicrosoftGroupsWebPartStrings' {
const strings: IMicrosoftGroupsWebPartStrings;
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,36 @@
{
"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": [
"es2015","es2017","dom"
]
},
"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
}
}