Merge pull request #3487 from ReactIntern/main

This commit is contained in:
Hugo Bernier 2023-03-11 15:13:05 -05:00 committed by GitHub
commit e46597d90d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 28416 additions and 23153 deletions

View File

@ -4,38 +4,26 @@
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.
- Toggle- View groups signed in user is a member of or all groups
- Group Name (hover for group description)
- Link to email
- Link to Group email
- Link to SharePoint site
- Link to calendar
- Link to Planner plan (if available)
- Group privacy
- Group privacy filter (Private/Public/All)
- Microsoft Teams team link for the Group
- Pagination (breaks results into pages and displays page numbers)
- Style toggle (Card/Table)
- Group display selection
1. Select 5, 10, or 15 groups to display per page for the Table display style
2. Select 6, 9, and 12 groups to display per page for the Card display style
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)
![picture of the web part in action](./assets/Table-Display.png)
![picture of the web part in action](./assets/Card-Display.png)
![picture of the web part in action](./assets/Theme-Display.png)
## Compatibility
| :warning: Important |
|:---------------------------|
| Every SPFx version is only compatible with specific version(s) of Node.js. In order to be able to build this sample, please ensure that the version of Node on your workstation matches one of the versions listed in this section. This sample will not work on a different version of Node.|
|Refer to <https://aka.ms/spfx-matrix> for more information on SPFx compatibility. |
![SPFx 1.12.1](https://img.shields.io/badge/SPFx-1.12.1-green.svg)
![Node.js v14 | v12 | v10](https://img.shields.io/badge/Node.js-v14%20%7C%20v12%20%7C%20v10-green.svg)
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
@ -45,16 +33,14 @@ Web part pulls all Microsoft 365 Groups and Teams that the logged in user has ac
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-yellow.svg "Only after API permissions granted")
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)
For more information about SPFx compatibility, please refer to <https://aka.ms/spfx-matrix>
## Applies to
- [SharePoint Framework Developer Preview](https://learn.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
- [SharePoint Framework](https://learn.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
- [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)
## Contributors
- [Alison Collins](https://github.com/ReactIntern) ([Blog](https://graphgod.dev), [LinkedIn](https://www.linkedin.com/in/alison-collins-53192b219/))
* [Alison Collins](https://github.com/ReactIntern) ([Blog](https://graphgod.dev), [LinkedIn](https://www.linkedin.com/in/alison-collins-53192b219/))
## Version history
@ -63,6 +49,7 @@ For more information about SPFx compatibility, please refer to <https://aka.ms/s
| 1.0.0 | April 16, 2021 | Initial release |
| 1.0.1 | August 1, 2021 | Fixed references to Office.com |
| 1.1.0 | October 8, 2021 | Upgraded to SPFx 1.12.1 for higher compatibility and added Teams Tab deployment support. |
| 2.0.0 | February 26, 2023 | Added new styles (card/table), added page display settings/pagination
## Prerequisites

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -9,7 +9,7 @@
"Web part pulls all Microsoft 365 Groups and Teams that the logged in user has access to view."
],
"creationDateTime": "2021-05-06",
"updateDateTime": "2021-10-08",
"updateDateTime": "2023-02-26",
"products": [
"SharePoint"
],

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,8 @@
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5"
"react-dom": "16.8.5",
"tslint-microsoft-contrib": "^6.2.0"
},
"resolutions": {
"@types/react": "16.8.8"

View File

@ -35,7 +35,7 @@
"scriptResources": {
"microsoft-groups-web-part": {
"type": "path",
"path": "microsoft-groups-web-part_da003a7fb7fd1f14fcb7.js"
"path": "microsoft-groups-web-part_c5a309120605c5a62341.js"
},
"@microsoft/sp-core-library": {
"type": "component",
@ -59,7 +59,7 @@
},
"MicrosoftGroupsWebPartStrings": {
"type": "path",
"path": "MicrosoftGroupsWebPartStrings_en-us_b41dd8b4c7f5f69692bd8f24e2b83745.js"
"path": "MicrosoftGroupsWebPartStrings_en-us_9cea220c7c0b6131e752012a44ddee44.js"
}
}
}

View File

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

File diff suppressed because one or more lines are too long

View File

@ -35,7 +35,7 @@
"scriptResources": {
"microsoft-groups-web-part": {
"type": "path",
"path": "microsoft-groups-web-part_da003a7fb7fd1f14fcb7.js"
"path": "microsoft-groups-web-part_c5a309120605c5a62341.js"
},
"@microsoft/sp-core-library": {
"type": "component",
@ -59,7 +59,7 @@
},
"MicrosoftGroupsWebPartStrings": {
"type": "path",
"path": "MicrosoftGroupsWebPartStrings_en-us_b41dd8b4c7f5f69692bd8f24e2b83745.js"
"path": "MicrosoftGroupsWebPartStrings_en-us_9cea220c7c0b6131e752012a44ddee44.js"
}
}
}

View File

@ -35,7 +35,7 @@
"scriptResources": {
"microsoft-groups-web-part": {
"type": "path",
"path": "microsoft-groups-web-part_da003a7fb7fd1f14fcb7.js"
"path": "microsoft-groups-web-part_c5a309120605c5a62341.js"
},
"@microsoft/sp-core-library": {
"type": "component",
@ -59,7 +59,7 @@
},
"MicrosoftGroupsWebPartStrings": {
"type": "path",
"path": "MicrosoftGroupsWebPartStrings_en-us_b41dd8b4c7f5f69692bd8f24e2b83745.js"
"path": "MicrosoftGroupsWebPartStrings_en-us_9cea220c7c0b6131e752012a44ddee44.js"
}
}
}

View File

@ -4,14 +4,19 @@ import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
PropertyPaneChoiceGroup,
PropertyPaneTextField,
PropertyPaneToggle
} from '@microsoft/sp-webpart-base';
import { IPropertyPaneChoiceGroupOption } from '@microsoft/sp-property-pane';
import * as strings from 'MicrosoftGroupsWebPartStrings';
import MicrosoftGroups from './components/MicrosoftGroups';
import MicrosoftGroups from './components/MicrosoftGroupsTeams';
export interface IMicrosoftGroupsWebPartProps {
description: string;
GroupDisplayTable: any;
GroupDisplayCard: any;
StyleToggle: boolean;
}
export default class MicrosoftGroupsWebPart extends BaseClientSideWebPart<IMicrosoftGroupsWebPartProps> {
@ -20,7 +25,10 @@ export default class MicrosoftGroupsWebPart extends BaseClientSideWebPart<IMicro
const element: React.ReactElement<IMicrosoftGroupsWebPartProps > = React.createElement(
MicrosoftGroups,
{
context: this.context
context: this.context,
GroupDisplayTable: this.properties.GroupDisplayTable,
GroupDisplayCard: this.properties.GroupDisplayCard,
StyleToggle: this.properties.StyleToggle
}
);
@ -34,7 +42,6 @@ export default class MicrosoftGroupsWebPart extends BaseClientSideWebPart<IMicro
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
@ -44,11 +51,28 @@ export default class MicrosoftGroupsWebPart extends BaseClientSideWebPart<IMicro
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
PropertyPaneToggle('StyleToggle', {
label: 'Style',
onText: 'Cards',
offText: 'Table'
}),
PropertyPaneChoiceGroup('GroupDisplayTable', {
label: 'Group Display - Table Style',
options: [
{ key: 5, text: '5 Groups', checked: true },
{ key: 10, text: '10 Groups'},
{ key: 15, text: '15 Groups' }
]
}),
PropertyPaneChoiceGroup('GroupDisplayCard', {
label: 'Group Display - Card Style',
options: [
{ key: 6, text: '6 Groups', checked: true },
{ key: 9, text: '9 Groups'},
{ key: 12, text: '12 Groups' }
]
})
]
}
]

View File

@ -0,0 +1,222 @@
import * as React from 'react';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './MicrosoftGroups.module.scss';
import { DocumentCard, DocumentCardDetails, Modal, Stack, ThemeProvider, TooltipHostBase } from 'office-ui-fabric-react';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { iconClass } from './MicrosoftGroupsTeams';
export interface IGraphConsumerProps {
context: WebPartContext;
GroupDisplay: number;
Mygroups: any [];
Allgroups:any[];
}
export interface IUserItem {
Topic: string;
DeliveryDate: Date;
}
export interface IGraphConsumerState {
mode: string;
title: string;
GroupResultsFiltered: any;
Next: number;
Min: number;
GroupDisplay: number;
}
export default class MicrosoftGroups extends React.Component<IGraphConsumerProps, IGraphConsumerState> {
public _plannerIDs = [];
public _arr = [];
public webAbsoluteURL: string = this.props.context.pageContext.web.absoluteUrl;
public TenantPathname: string = this.webAbsoluteURL.split('//')[1].split('/')[0];
public TenantEmail: string = this.props.context.pageContext.user.loginName.split('@')[1];
constructor(props) {
super(props);
this.state = {
mode: 'All',
title: 'Groups In My Organization',
GroupResultsFiltered: [],
Next: 6,
Min: 0,
GroupDisplay: 6
};
}
public SwitchGroupList() {
if (this.state.title === 'Groups In My Organization') {
this.setState({ title: "My Groups" });
this.SwitchGroupList2(this.state.mode, "My Groups");
}else {
this.setState({ title: 'Groups In My Organization' });
this.SwitchGroupList2(this.state.mode, 'Groups In My Organization');
}
}
public SwitchGroupList2(Switch:string, title) {
if (Switch === 'All') {
if(title === 'My Groups') {
this.setState({ GroupResultsFiltered: this.props.Mygroups });
} else{
this.setState({ GroupResultsFiltered: this.props.Allgroups });
}
}
else {
var SwitchedValue;
if(title === 'My Groups') {
SwitchedValue = this.props.Mygroups.filter(item => item.Visibility === Switch);
}
else{
SwitchedValue = this.props.Allgroups.filter(item => item.Visibility === Switch);
}
this.setState({ GroupResultsFiltered: SwitchedValue});
}
var nextVariable;
if(this.state.GroupDisplay != undefined) {
if(!(this.props.GroupDisplay === undefined)) {nextVariable = this.props.GroupDisplay;} else {nextVariable = 5;}
this.setState({ mode: Switch, Next:nextVariable, Min: 0});
}
else {
nextVariable = 5;
this.setState({ mode: Switch, Next:nextVariable, Min: 0, GroupDisplay: 5});}
}
public componentDidMount() {
if (!(this.props.GroupDisplay === undefined || this.props.GroupDisplay ===null)){
this.SwitchGroupList2(this.state.mode, this.state.title);
this.setState({GroupDisplay: this.props.GroupDisplay, Min:0, Next: this.props.GroupDisplay}); }
else {
this.SwitchGroupList2(this.state.mode, this.state.title);
this.setState({GroupDisplay: 5, Min: 0, Next: 5});
}
}
public Next(GroupsFiltered) {
var array = [],
count = 0,
min = this.state.Next,
max = min + (this.state.GroupDisplay + 1);
GroupsFiltered.map(Group => {
count = count + 1;
if (count > min && count < max) {
array.push(Group);
}
});
var newVal = this.state.Next + this.state.GroupDisplay;
this.setState({ Next: newVal, Min: min});
}
public Back(GroupsFiltered) {
// comments are as going forward one page and then back. GroupDisplay is set to the default at 5
var array = [], //create an array to store the new groups to display
max, min, count = 0, //min and max for the range of groups by number and count to get the current level of mapping
Above = this.state.Next + this.state.GroupDisplay, //say it's 15
FilteredL = this.state.GroupResultsFiltered.length, // How many groups we have in total, say we have 12 total.
i = Math.floor(FilteredL / (this.state.GroupDisplay)), //Our group total divided by the limit per page, rounding up so we don't miss groups, 5 goes into 12 twice
ifull = i * (this.state.GroupDisplay), //we basically multiply it back out, finding how many pages of full groups we can have. 10
Leftover = FilteredL - ifull, // Finding the difference/leftover when we do the math: 12 - 10 = 2
difference = (this.state.GroupDisplay) - Leftover; // Finding how much we're missing in the last page: 5 - 2 = 3
if((this.state.Next + (this.state.GroupDisplay)) - difference > FilteredL){
/*if 10 (remember we're on page 2) + 5 (the current display setting) - 3 (the difference between a full page) > Total Groups
//This step matters if you are going back from the last page!! Remember, when we go to the next page we add in increments of 5
That means if I were to go to the last page, my Next would be 15 and this statement would be true-
15 + 5 - 3 = 17*/
max = (FilteredL - Leftover) + 1; // 12 - 2 + 1. We get it back into the 5 increment setting.
min = max - this.state.GroupDisplay - 1; // Subtract 5 from the max so we can have a range of 5.
/*We just add one because when we map through the Groups we don't have an "or equals to" so it has to be 1 above and under*/
}
else {
// for when it's not the last page we're going back from
max = this.state.Next - this.state.GroupDisplay;
min = max - this.state.GroupDisplay;
}
GroupsFiltered.map(Group => {
count = count + 1;
if (count > min && count < max) {
array.push(Group);
}
});
var newVal = this.state.Next - (this.state.GroupDisplay);
this.setState({ Next: newVal, Min: min });
}
public componentDidUpdate(prevProps: Readonly<IGraphConsumerProps>): void {
if(prevProps.GroupDisplay !== this.props.GroupDisplay) {
this.setState({Next: this.props.GroupDisplay, Min: 0, GroupDisplay: this.props.GroupDisplay});
}
if(prevProps.Allgroups !== this.props.Allgroups || prevProps.Mygroups !== this.props.Mygroups) {
this.SwitchGroupList2(this.state.mode, this.state.title);
}
}
public render(): React.ReactElement<IGraphConsumerProps> {
var i = 0,
Replaceregex = /\s+/g;
return <div className={styles.Container}>
<div className={styles.tableCaptionStyle}>{this.state.title}
<div>
{this.state.mode === 'Public' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Public', this.state.title)}>Public</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Public', this.state.title)}>Public</button>}
{this.state.mode === 'All' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('All', this.state.title)}>All</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('All', this.state.title)}>All</button>}
{this.state.mode === 'Private' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Private', this.state.title)}>Private</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Private', this.state.title)}>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}>
{ this.state.GroupResultsFiltered.map(Group => {
var GroupEmailSplit = Group.Mail.split("@");
Group.Mail = GroupEmailSplit[0];
i = i + 1;
if ( i <= this.state.Next && i >= this.state.Min +1) {
return <DocumentCard
style={{boxShadow: '0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1)',
width:'31.65%',
height: '144px',
paddingBottom: '15px',
float: 'left',
margin: '6px'
}}
>
<h3 style={{marginBottom: '5px'}}>{Group.Name}</h3>
<DocumentCardDetails>
<div style={{padding: '0px 4px 0px 4px'}}>{Group.Description}</div>
<div style={{display:'table-row'}}>
<a href={`https://outlook.office.com/mail/group/${this.TenantEmail}/${Group.Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7', margin:'2px', marginTop:'10px' }} iconName="OutlookLogo"></Icon>
</a>
<a href={`https://${this.TenantPathname}/sites/${Group.Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90', margin:'2px', marginTop:'10px' }} iconName="SharePointLogo"></Icon>
</a>
<a href={`https://outlook.office.com/calendar/group/${this.TenantEmail}/${Group.Name.replace(Replaceregex, '')}/view/week`}>
<Icon className={iconClass} style={{ color: '#119AE2', margin:'2px', marginTop:'10px' }} iconName="Calendar"></Icon>
</a>
<>
{Group.Planner === undefined ? <></> : <a href={Group.Planner}>
<Icon className={iconClass} style={{ color: '#077D3F', margin:'2px', marginTop:'10px' }} iconName="ViewListTree"></Icon></a>}
</>
<>{Group.WebUrl === undefined || Group.WebUrl === null ? <></> :
<a href={`${Group.WebUrl}`}>
<Icon className={iconClass} style={{ color: '#424AB5', margin:'2px', marginTop:'10px' }} iconName="TeamsLogo"></Icon>
</a>}</>
</div></DocumentCardDetails>
</DocumentCard>;
}})}
</div>
{this.state.GroupResultsFiltered.length === 0 ? <div>There are no items with the selected filters</div>:<></>}
<div className={styles.tableStyle}>
<div style={{display:'table-row'}}>
<div style={{position: 'relative', textAlign:'right', width:'45%', display:'table-cell'}}>
<button disabled={this.state.Next === (this.state.GroupDisplay)} onClick={() => this.Back(this.state.GroupResultsFiltered)}>Back</button>
</div>
<div style={{padding: 8}}>{this.state.Next/this.state.GroupDisplay} of {Math.ceil(this.state.GroupResultsFiltered.length/this.state.GroupDisplay)}</div>
<div style={{position: 'relative', textAlign:'left', width:'45%', display:'table-cell'}}>
<button disabled={this.state.Next >= this.state.GroupResultsFiltered.length} onClick={() => this.Next(this.state.GroupResultsFiltered)}>Next</button>
</div> </div></div>
</div>;
}
}

View File

@ -1,243 +1,221 @@
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';
import { iconClass } from './MicrosoftGroupsTeams';
export interface IGraphConsumerProps {
context: WebPartContext;
GroupDisplay: number;
Mygroups: any [];
Allgroups:any[];
}
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;
GroupResultsFiltered: any;
Next: number;
Min: number;
GroupDisplay: number;
}
export default class MicrosoftGroups extends React.Component<IGraphConsumerProps, IGraphConsumerState> {
public _plannerIDs = [];
public _arr = [];
private graphClient: MSGraphClient = null;
public webAbsoluteURL: string = this.props.context.pageContext.web.absoluteUrl;
public TenantPathname: string = this.webAbsoluteURL.split('//')[1].split('/')[0];
public TenantEmail: string = this.props.context.pageContext.user.loginName.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: []
GroupResultsFiltered: [],
Next: 5,
Min: 0,
GroupDisplay: 5,
};
}
public SwitchGroupList() {
if (this.state.title === 'Groups In My Organization') {
this.setState({ title: "My Groups" });
}
else {
this.SwitchGroupList2(this.state.mode, "My Groups");
}else {
this.setState({ title: 'Groups In My Organization' });
this.SwitchGroupList2(this.state.mode, 'Groups In My Organization');
}
}
public SwitchGroupList2(Switch) {
public SwitchGroupList2(Switch:string, title) {
if (Switch === 'All') {
this.setState({ AllGroupsresultsFiltered: this.state.AllGroupsresults });
this.setState({ MyGroupResultsFiltered: this.state.MyGroupResults });
if(title === 'My Groups') {
this.setState({ GroupResultsFiltered: this.props.Mygroups });
} else{
this.setState({ GroupResultsFiltered: this.props.Allgroups });
}
}
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.TenantEmail}/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) {
var SwitchedValue;
if(title === 'My Groups') {
SwitchedValue = this.props.Mygroups.filter(item => item.Visibility === Switch);
}
});
}
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) {
// Do nothing
}
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();
}
});
});
else{
SwitchedValue = this.props.Allgroups.filter(item => item.Visibility === Switch);
}
this.setState({ GroupResultsFiltered: SwitchedValue});
}
var nextVariable;
if(this.state.GroupDisplay != undefined) {
if(!(this.props.GroupDisplay === undefined)) {nextVariable = this.props.GroupDisplay;} else {nextVariable = 5;}
this.setState({ mode: Switch, Next:nextVariable, Min: 0});
}
else {
nextVariable = 5;
this.setState({ mode: Switch, Next:nextVariable, Min: 0, GroupDisplay: 5});}
}
public componentDidMount() {
this.GetGroups();
if (!(this.props.GroupDisplay === undefined || this.props.GroupDisplay ===null)){
this.SwitchGroupList2(this.state.mode, this.state.title);
this.setState({GroupDisplay: this.props.GroupDisplay, Min:0, Next: this.props.GroupDisplay}); }
else {
this.SwitchGroupList2(this.state.mode, this.state.title);
this.setState({GroupDisplay: 5, Min: 0, Next: 5});
}
}
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>}
public Next(GroupsFiltered) {
var array = [],
count = 0,
min = this.state.Next,
max = min + (this.state.GroupDisplay + 1);
GroupsFiltered.map(Group => {
count = count + 1;
if (count > min && count < max) {
array.push(Group);
}
});
var newVal = this.state.Next + this.state.GroupDisplay;
this.setState({ Next: newVal, Min: min});
}
{this.state.mode === 'Private' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Private')}>Private</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Private')}>Private</button>}
public Back(GroupsFiltered) {
// comments are as going forward one page and then back. GroupDisplay is set to the default at 5
var array = [], //create an array to store the new groups to display
max, min, count = 0, //min and max for the range of groups by number and count to get the current level of mapping
Above = this.state.Next + this.state.GroupDisplay, //say it's 15
FilteredL = this.state.GroupResultsFiltered.length, // How many groups we have in total, say we have 12 total.
i = Math.floor(FilteredL / (this.state.GroupDisplay)), //Our group total divided by the limit per page, rounding up so we don't miss groups, 5 goes into 12 twice
ifull = i * (this.state.GroupDisplay), //we basically multiply it back out, finding how many pages of full groups we can have. 10
Leftover = FilteredL - ifull, // Finding the difference/leftover when we do the math: 12 - 10 = 2
difference = (this.state.GroupDisplay) - Leftover; // Finding how much we're missing in the last page: 5 - 2 = 3
if((this.state.Next + (this.state.GroupDisplay)) - difference > FilteredL){
/*if 10 (remember we're on page 2) + 5 (the current display setting) - 3 (the difference between a full page) > Total Groups
//This step matters if you are going back from the last page!! Remember, when we go to the next page we add in increments of 5
That means if I were to go to the last page, my Next would be 15 and this statement would be true-
15 + 5 - 3 = 17*/
max = (FilteredL - Leftover) + 1; // 12 - 2 + 1. We get it back into the 5 increment setting.
min = max - this.state.GroupDisplay - 1; // Subtract 5 from the max so we can have a range of 5.
/*We just add one because when we map through the Groups we don't have an "or equals to" so it has to be 1 above and under*/
}
else {
// for when it's not the last page we're going back from
max = this.state.Next - this.state.GroupDisplay;
min = max - this.state.GroupDisplay;
}
GroupsFiltered.map(Group => {
count = count + 1;
if (count > min && count < max) {
array.push(Group);
}
});
var newVal = this.state.Next - (this.state.GroupDisplay);
this.setState({ Next: newVal, Min: min });
}
public componentDidUpdate(prevProps: Readonly<IGraphConsumerProps>): void {
if(prevProps.GroupDisplay !== this.props.GroupDisplay) {
this.setState({Next: this.props.GroupDisplay, Min: 0, GroupDisplay: this.props.GroupDisplay});
}
if(prevProps.Allgroups !== this.props.Allgroups || prevProps.Mygroups !== this.props.Mygroups) {
this.SwitchGroupList2(this.state.mode, this.state.title);
}
}
public render(): React.ReactElement<IGraphConsumerProps> {debugger;
var NextButton,
BackButton,
i = 0,
Replaceregex = /\s+/g;
this.state.Next >= this.state.GroupResultsFiltered.length ? NextButton = styles.DisButton : NextButton = styles.NavButton;
this.state.Next === (this.state.GroupDisplay) ? BackButton = styles.DisButton : BackButton = styles.NavButton;
return <div className={styles.Container}>
<div className={styles.tableCaptionStyle}>{this.state.title}
<div>
{this.state.mode === 'Public' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Public', this.state.title)}>Public</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Public', this.state.title)}>Public</button>}
{this.state.mode === 'All' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('All', this.state.title)}>All</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('All', this.state.title)}>All</button>}
{this.state.mode === 'Private' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Private', this.state.title)}>Private</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Private', this.state.title)}>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}>
<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}>Teams</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>Visibility</div>
</div>
{ this.state.GroupResultsFiltered.map(Group => {
var GroupEmailSplit = Group.Mail.split("@");
Group.Mail = GroupEmailSplit[0];
i = i + 1;
if ( i <= this.state.Next && i >= this.state.Min +1) {
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.office.com/mail/group/${this.TenantEmail}/${Group.Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon>
</a>
<a className={styles.Center} href={`https://${this.TenantPathname}/sites/${Group.Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office.com/calendar/group/${this.TenantEmail}/${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>
{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.office.com/mail/group/${this.TenantEmail}/${Group.Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon>
</a>
<a className={styles.Center} href={`https://${this.TenantPathname}/sites/${Group.Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office.com/calendar/group/${this.TenantEmail}/${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 className={styles.Center}>{Group.WebUrl === undefined || Group.WebUrl === null ? <div></div> :
<a href={`${Group.WebUrl}`}>
<Icon className={iconClass} style={{ color: '#424AB5' }} iconName="TeamsLogo"></Icon>
</a>}</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>{Group.Visibility}</div>
</div>;}})}
</div>
{this.state.GroupResultsFiltered.length === 0 ? <div>There are no items with the selected filters</div>:<></>}
<div className={styles.tableStyle}>
<div style={{display:'table-row'}}>
<div style={{position: 'relative', textAlign:'right', width:'45%', display:'table-cell'}}>
<button className={BackButton} disabled={this.state.Next === (this.state.GroupDisplay)} onClick={() => this.Back(this.state.GroupResultsFiltered)}>Back</button>
</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.office.com/mail/group/${this.TenantEmail}/${Group.Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon>
</a>
<a className={styles.Center} href={`https://${this.TenantPathname}/sites/${Group.Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office.com/calendar/group/${this.TenantEmail}/${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 style={{padding: 8}}>{this.state.Next/this.state.GroupDisplay} of {Math.ceil(this.state.GroupResultsFiltered.length/this.state.GroupDisplay)}</div>
<div style={{position: 'relative', textAlign:'left', width:'45%', display:'table-cell'}}>
<button className={NextButton} disabled={this.state.Next >= this.state.GroupResultsFiltered.length} onClick={() => this.Next(this.state.GroupResultsFiltered)}>Next</button>
</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>{Group.Visibility}</div>
</div>;
})}
</div>;
</div>;
}
</div>
</div>
</div>;
}
}

View File

@ -1,92 +1,83 @@
@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;
.Center {
padding: 3px 1px 0px;
margin-left: 26px;
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;
max-width: 113px;
word-break: break-word;
}
.SwitchGroups{
position:absolute;
bottom:6px;
right:6px;
font-size:16px;
background-color: transparent;
border: none;
cursor:pointer;
color:white;
}
.SwitchGroups:hover{
text-decoration: underline;
}
.headerStyle{
}
.SwitchGroups:hover{
text-decoration: underline;
}
.NavButton {
padding: 8px;
background-color: "[theme: themePrimary, default: #0078d7]";
color: white;
border: none;
}
.DisButton {
padding: 8px;
color: white;
border: none;
background-color: "[theme: themeLight, default: #0078d7]";
}
.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 ;
}
border: solid ;
text-align : center ;
width : 100px ;
height : 30px ;
padding-top : 10px ;
color : white ;
}
.tableStyle{
display: table;
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: none;
text-align:center ;
height:79px;
padding-top:9px;
color:white;
border-top: 2px solid white;
border-bottom: 2px solid white;
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;
@ -94,22 +85,14 @@
padding: 20px;
}
.CellStyle{
display: table-cell ;
border: solid ;
border-color : white ;
text-align : center ;
min-width : 75px ;
height : 30px ;
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 ;
padding-top: 10px;
}
.column {
@include ms-Grid-col;
@ -118,64 +101,10 @@
@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 {
.Container {
text-align: center;
vertical-align: middle;
}
.none {
display:none;
height:100%
}
.CenterRight {
border-right:none;
@ -184,19 +113,6 @@
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;
}
@ -206,7 +122,6 @@
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;
@ -218,7 +133,6 @@
background-color: $ms-color-themeDark;
color: #fff;
text-align: center;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 86%;
@ -233,7 +147,7 @@
background-color: "[theme: themeLight, default: #0078d7]";
}
.MainViewCenter {
width: 50%;
width: 45%;
padding-top: 10px;
padding-bottom: 10px;
display: table-cell;
@ -242,10 +156,8 @@
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{
@ -256,170 +168,22 @@
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;
padding: 4px;
}
.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;}
background-color: "[theme: themeDark, default: #0078d7]";
color: #fff;
border: none;
margin-right: 4px;
margin-top: 9px;
padding: 4px;
}
.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

@ -1,36 +0,0 @@
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,214 @@
import * as React from 'react';
import { MSGraphClient } from "@microsoft/sp-http";
import { WebPartContext } from '@microsoft/sp-webpart-base';
import GraphConsumer from './Microsoft365Groups';
import CardStyle from './CardStyle';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
export interface IMicrosoftGroupsProps {
context: WebPartContext;
GroupDisplayTable: any;
GroupDisplayCard: any;
StyleToggle: boolean;
}
export interface IMicrosoftGroupsState {
AllGroupsresults: any;
MyGroupResults: any;
GroupDisplayTable: number;
GroupDisplayCard: number;
StyleToggle: boolean;
}
export const iconClass = mergeStyles({
fontSize: 32,
height: 32,
width: 32
});
export default class MicrosoftGroups extends React.Component<IMicrosoftGroupsProps, IMicrosoftGroupsState> {
public _plannerIDs = [];
public _arr = [];
private graphClient: MSGraphClient = null;
public webAbsoluteURL: string = this.props.context.pageContext.web.absoluteUrl;
public TenantPathname: string = this.webAbsoluteURL.split('//')[1].split('/')[0];
public TenantEmail: string = this.props.context.pageContext.user.loginName.split('@')[1];
constructor(props) {
super(props);
this.GetPlanner = this.GetPlanner.bind(this);
this.GetGroups = this.GetGroups.bind(this);
this.state = {
AllGroupsresults: [],
MyGroupResults: [],
GroupDisplayTable: 5,
GroupDisplayCard: 6,
StyleToggle: false
};
}
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.TenantEmail}/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;
}
});
}
this.setState({ MyGroupResults: this.state.MyGroupResults });
} catch (error) {}
});
this.GetTeams();
}
public async GetTeams() {
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.GetTeamsURL(array);
}
});
});
}
public GetTeamsURL(Teams) {
Teams.map(Team => {
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`/teams/${Team.Id}/?$select=webUrl`)
.version("v1.0")
.get((err, res) => {
if (err) {
return;
}
if (res) {
var URL = (res.webUrl.split('conversations')[0] + '/General' + res.webUrl.split('conversations')[1]),
webUrl = { WebUrl: URL },
Results = Object.assign(Team, { WebUrl: webUrl });
Team = Results;
this.state.MyGroupResults.map(Group => {
if (Group.Name === Team.Name) {
var Results2 = Object.assign(Group, webUrl);
Group = Results2;
}
});
this.state.AllGroupsresults.map(Group => {
if (Group.Name === Team.Name) {
var Results2 = Object.assign(Group, webUrl);
Group = Results2;
}
});
this.setState({ AllGroupsresults: this.state.AllGroupsresults, MyGroupResults: this.state.MyGroupResults});
}
});
});
});
}
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({ 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) {
// Do nothing
}
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 });
this.GetPlanner();
}
});
});
}
public componentDidMount() {
this.GetGroups();
this.setState({GroupDisplayTable: this.props.GroupDisplayTable, GroupDisplayCard: this.props.GroupDisplayCard});
}
public componentDidUpdate(prevProps: Readonly<IMicrosoftGroupsProps>): void {
if(prevProps.GroupDisplayTable !== this.props.GroupDisplayTable) { //needed to prevent exceeding maximum update update depth
this.setState({GroupDisplayTable: this.props.GroupDisplayTable});
}
if(prevProps.GroupDisplayCard !== this.props.GroupDisplayCard) {
this.setState({GroupDisplayCard: this.props.GroupDisplayCard});
}
if (prevProps.StyleToggle !== this.props.StyleToggle && this.props.StyleToggle!== undefined){
this.setState({StyleToggle: this.props.StyleToggle});
}
}
public render(): React.ReactElement<IMicrosoftGroupsProps> {
return <div>
{this.state.StyleToggle === false ?
<GraphConsumer
context={this.props.context}
GroupDisplay={this.state.GroupDisplayTable}
Mygroups={this.state.MyGroupResults}
Allgroups={this.state.AllGroupsresults}/>
:
<CardStyle
context={this.props.context}
GroupDisplay={this.props.GroupDisplayCard}
Mygroups={this.state.MyGroupResults}
Allgroups={this.state.AllGroupsresults}/>
}
</div>;
}
}

View File

@ -1,202 +0,0 @@
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 webAbsoluteURL: string = this.props.context.pageContext.web.absoluteUrl;
public TenantPathname: string = this.webAbsoluteURL.split('//')[1].split('/')[0];
public TenantEmail: string = this.props.context.pageContext.user.loginName.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.TenantEmail}/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.office.com/mail/group/${this.TenantEmail}/${Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon></a>
<a className={styles.Center} href={`https://${this.TenantPathname}/sites/${Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office.com/calendar/group/${this.TenantEmail}/${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

@ -1,7 +1,6 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"PropertyPaneDescription": "Your Microsoft Groups and Teams",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -33,4 +33,4 @@
"node_modules",
"lib"
]
}
}

View File

@ -17,7 +17,7 @@
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-use-before-declare": false,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,