Updated sample.json and fixed linting issues

This commit is contained in:
Hugo Bernier 2023-03-11 15:12:16 -05:00
parent 5dbd5bcdfa
commit a7f9d8183d
5 changed files with 81 additions and 84 deletions

View File

@ -1,21 +1,22 @@
# 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.
- Toggle- View groups signed in user is a member of or all groups
- Group Name (hover for group description)
- Link to Group email
- Link to SharePoint site
- Link to calendar
- Link to Planner plan (if available)
- 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
- Toggle- View groups signed in user is a member of or all groups
- Group Name (hover for group description)
- Link to Group email
- Link to SharePoint site
- Link to calendar
- Link to Planner plan (if available)
- 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. Select 6, 9, and 12 groups to display per page for the Card display style
![picture of the web part in action](./assets/Table-Display.png)
![picture of the web part in action](./assets/Card-Display.png)
@ -23,8 +24,8 @@ Web part pulls all Microsoft 365 Groups and Teams that the logged in user has ac
## 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)
![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)
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%202019-Not%20compatible-red.svg)
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%202016%20(Feature%20Pack%202)-Not%20compatible-red.svg)
@ -34,14 +35,12 @@ Web part pulls all Microsoft 365 Groups and Teams that the logged in user has ac
## 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)
- [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
## Contributors
Solution|Author(s)
--------|---------
React-Groups-Teams | [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
@ -82,7 +81,7 @@ React-Groups-Teams | [Alison Collins](https://github.com/ReactIntern) ([Blog](ht
- 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
> This sample can also be opened with [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview). Visit https://aka.ms/spfx-devcontainer for further instructions.
> This sample can also be opened with [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview). Visit <https://aka.ms/spfx-devcontainer> for further instructions.
## Features
@ -95,7 +94,7 @@ This sample illustrates the following concepts on top of the SharePoint Framewor
- 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
- Passing web part properties to React components
## Video
@ -116,10 +115,8 @@ For questions regarding this sample, [create a new question](https://github.com/
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Aenhancement%2Csample%3A%20react-groups-teams&template=question.yml&sample=react-groups-teams&authors=@ReactIntern&title=react-groups-teams%20-%20).
## 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.**
<img src="https://pnptelemetry.azurewebsites.net/sp-dev-fx-webparts/samples/react-groups-teams" />

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"
],

View File

@ -49,7 +49,7 @@ export default class MicrosoftGroups extends React.Component<IGraphConsumerProps
this.setState({ title: 'Groups In My Organization' });
this.SwitchGroupList2(this.state.mode, 'Groups In My Organization');
}
}
public SwitchGroupList2(Switch:string, title) {
if (Switch === 'All') {
@ -57,7 +57,7 @@ export default class MicrosoftGroups extends React.Component<IGraphConsumerProps
this.setState({ GroupResultsFiltered: this.props.Mygroups });
} else{
this.setState({ GroupResultsFiltered: this.props.Allgroups });
}
}
}
else {
var SwitchedValue;
@ -68,10 +68,10 @@ export default class MicrosoftGroups extends React.Component<IGraphConsumerProps
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;}
if(!(this.props.GroupDisplay === undefined)) {nextVariable = this.props.GroupDisplay;} else {nextVariable = 5;}
this.setState({ mode: Switch, Next:nextVariable, Min: 0});
}
else {
@ -81,10 +81,10 @@ export default class MicrosoftGroups extends React.Component<IGraphConsumerProps
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}) }
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})
this.setState({GroupDisplay: 5, Min: 0, Next: 5});
}
}
@ -113,18 +113,18 @@ public Back(GroupsFiltered) {
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((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.
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
max = this.state.Next - this.state.GroupDisplay;
min = max - this.state.GroupDisplay;
}
GroupsFiltered.map(Group => {
@ -140,7 +140,7 @@ 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) {
if(prevProps.Allgroups !== this.props.Allgroups || prevProps.Mygroups !== this.props.Mygroups) {
this.SwitchGroupList2(this.state.mode, this.state.title);
}
}
@ -151,17 +151,17 @@ public render(): React.ReactElement<IGraphConsumerProps> {
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> :
{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> :
{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> :
{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' ?
<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}>
@ -173,8 +173,8 @@ public render(): React.ReactElement<IGraphConsumerProps> {
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',
height: '144px',
paddingBottom: '15px',
float: 'left',
margin: '6px'
}}
@ -182,7 +182,7 @@ public render(): React.ReactElement<IGraphConsumerProps> {
<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>
@ -198,21 +198,21 @@ public render(): React.ReactElement<IGraphConsumerProps> {
{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 ? <></> :
<>{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>
</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={{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>
<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>

View File

@ -47,7 +47,7 @@ export default class MicrosoftGroups extends React.Component<IGraphConsumerProps
this.setState({ title: 'Groups In My Organization' });
this.SwitchGroupList2(this.state.mode, 'Groups In My Organization');
}
}
public SwitchGroupList2(Switch:string, title) {
if (Switch === 'All') {
@ -55,7 +55,7 @@ export default class MicrosoftGroups extends React.Component<IGraphConsumerProps
this.setState({ GroupResultsFiltered: this.props.Mygroups });
} else{
this.setState({ GroupResultsFiltered: this.props.Allgroups });
}
}
}
else {
var SwitchedValue;
@ -66,10 +66,10 @@ export default class MicrosoftGroups extends React.Component<IGraphConsumerProps
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;}
if(!(this.props.GroupDisplay === undefined)) {nextVariable = this.props.GroupDisplay;} else {nextVariable = 5;}
this.setState({ mode: Switch, Next:nextVariable, Min: 0});
}
else {
@ -79,10 +79,10 @@ export default class MicrosoftGroups extends React.Component<IGraphConsumerProps
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}) }
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})
this.setState({GroupDisplay: 5, Min: 0, Next: 5});
}
}
@ -111,18 +111,18 @@ public Back(GroupsFiltered) {
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((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.
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
max = this.state.Next - this.state.GroupDisplay;
min = max - this.state.GroupDisplay;
}
GroupsFiltered.map(Group => {
@ -138,7 +138,7 @@ 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) {
if(prevProps.Allgroups !== this.props.Allgroups || prevProps.Mygroups !== this.props.Mygroups) {
this.SwitchGroupList2(this.state.mode, this.state.title);
}
}
@ -153,17 +153,17 @@ public render(): React.ReactElement<IGraphConsumerProps> {debugger;
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> :
{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> :
{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> :
{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' ?
<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}>
@ -197,23 +197,23 @@ public render(): React.ReactElement<IGraphConsumerProps> {debugger;
{Group.Planner === undefined ? <div></div> : <a href={Group.Planner}>
<Icon className={iconClass} style={{ color: '#077D3F' }} iconName="ViewListTree"></Icon></a>}
</div>
<div className={styles.Center}>{Group.WebUrl === undefined || Group.WebUrl === null ? <div></div> :
<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>;}})}
</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={{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>
<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>
</div>
</div>
</div>;

View File

@ -43,7 +43,7 @@ export default class MicrosoftGroups extends React.Component<IMicrosoftGroupsPro
StyleToggle: false
};
}
public async GetPlanner() {
this.state.MyGroupResults.map(async GroupId => {
try {
@ -67,7 +67,7 @@ export default class MicrosoftGroups extends React.Component<IMicrosoftGroupsPro
}
});
}
this.setState({ MyGroupResults: this.state.MyGroupResults });
} catch (error) {}
});
@ -190,25 +190,25 @@ public componentDidUpdate(prevProps: Readonly<IMicrosoftGroupsProps>): void {
this.setState({GroupDisplayCard: this.props.GroupDisplayCard});
}
if (prevProps.StyleToggle !== this.props.StyleToggle && this.props.StyleToggle!== undefined){
this.setState({StyleToggle: this.props.StyleToggle})
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}
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}
:
<CardStyle
context={this.props.context}
GroupDisplay={this.props.GroupDisplayCard}
Mygroups={this.state.MyGroupResults}
Allgroups={this.state.AllGroupsresults}/>
}
</div>
}
</div>;
}
}
}