Skip to content

Commit

Permalink
Merge pull request #396 from waldekmastykarz/enh-leads-multitabs
Browse files Browse the repository at this point in the history
Extends Leads with multitab setup for personal Teams app
  • Loading branch information
VesaJuvonen authored May 2, 2020
2 parents eccb80a + 4b1e820 commit abd9993
Show file tree
Hide file tree
Showing 24 changed files with 544 additions and 58 deletions.
2 changes: 1 addition & 1 deletion solutions/LeadsLOBSolution/.yo-rc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"@microsoft/generator-sharepoint": {
"plusBeta": true,
"isCreatingSolution": true,
"isCreatingSolution": false,
"environment": "spo",
"version": "1.10.0",
"libraryName": "leads-lob",
Expand Down
5 changes: 4 additions & 1 deletion solutions/LeadsLOBSolution/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Demo solution demonstrating how to build a solution using SharePoint Framework a

![Teams Tab](./assets/teams-tab-solution.png)

![Personal Teams app](./assets/personal-app-solution.png)

> This solution was originally demonstrated in the SharePoint Conference 2018 keynote and also in the Ignite 2018. As part of the release of SharePoint Framework 1.7, it can be finally shared as a generic demo for the community.
## Installation
Expand All @@ -24,7 +26,7 @@ By default web part is using "demo mode", which does not require any additional

## Solution information

![SPFx v1.7.0](https://img.shields.io/badge/SPFx-1.7.0-green.svg)
![SPFx v1.10.0](https://img.shields.io/badge/SPFx-1.10.0-green.svg)

### Compatibility

Expand All @@ -38,6 +40,7 @@ This solution is compatible with SharePoint Online. Teams capability only works

Version|Date|Comments
-------|----|--------
1.1.0|May 1, 2020|Extended with a multi-tab personal Teams app configuration
1.0.0|November 21, 2018|Updated to match v1.7 experience and tested in RTM

## Disclaimer
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 11 additions & 2 deletions solutions/LeadsLOBSolution/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@
"manifest": "./src/webparts/leads/LeadsWebPart.manifest.json"
}
]
},
"leads-settings-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/leadsSettings/LeadsSettingsWebPart.js",
"manifest": "./src/webparts/leadsSettings/LeadsSettingsWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"LeadsWebPartStrings": "lib/webparts/leads/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"LeadsSettingsWebPartStrings": "lib/webparts/leadsSettings/loc/{locale}.js"
}
}
}
2 changes: 1 addition & 1 deletion solutions/LeadsLOBSolution/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion solutions/LeadsLOBSolution/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "leads-lob",
"main": "lib/index.js",
"version": "0.0.1",
"version": "1.1.0",
"private": true,
"engines": {
"node": ">=0.10.0"
Expand Down
Binary file not shown.
57 changes: 57 additions & 0 deletions solutions/LeadsLOBSolution/src/LeadsSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";

export interface ILeadsSettings {
demo: boolean;
quarterlyOnly: boolean;
region?: string;
}

export class LeadsSettings {
private static settingsStorageKey: string = 'LeadsSettings';

public static getSettings(): ILeadsSettings {
const defaultSettings: ILeadsSettings = {
demo: true,
quarterlyOnly: true
};

try {
const settingsString: string = window.localStorage.getItem(this.settingsStorageKey);
if (settingsString) {
const settings: ILeadsSettings = JSON.parse(settingsString);
return settings;
}
}
catch (e) { }

return defaultSettings;
}

public static setSettings(settings: ILeadsSettings): void {
window.localStorage.setItem(this.settingsStorageKey, JSON.stringify(settings));
}

public static getLeadsApiUrl(spHttpClient: SPHttpClient, siteUrl: string): Promise<string> {
return new Promise<string>((resolve: (leadsApiUrl: string) => void, reject: (error: any) => void): void => {
spHttpClient
.get(`${siteUrl}/_api/web/GetStorageEntity('LeadsApiUrl')`, SPHttpClient.configurations.v1)
.then((res: SPHttpClientResponse) => {
if (!res.ok) {
return reject(res.statusText);
}

return res.json();
})
.then((property: { Value?: string }) => {
if (property.Value) {
resolve(property.Value);
}
else {
reject('Property not found');
}
}, (error: any): void => {
reject(error);
});
});
}
}
87 changes: 61 additions & 26 deletions solutions/LeadsLOBSolution/src/webparts/leads/LeadsWebPart.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { Version, UrlQueryParameterCollection } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart
} from '@microsoft/sp-webpart-base';
import {
IPropertyPaneConfiguration,
PropertyPaneButtonType,
PropertyPaneButton,
PropertyPaneDropdown,
PropertyPaneLabel,
PropertyPaneTextField,
PropertyPaneToggle
import {
IPropertyPaneConfiguration,
PropertyPaneButtonType,
PropertyPaneButton,
PropertyPaneDropdown,
PropertyPaneLabel,
PropertyPaneTextField,
PropertyPaneToggle
} from "@microsoft/sp-property-pane";

import * as strings from 'LeadsWebPartStrings';
import { Leads, ILeadsProps } from './components/Leads';
import { Leads, ILeadsProps, LeadView } from './components/Leads';
import { SPHttpClient, HttpClientResponse, HttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
import { ILeadsSettings, LeadsSettings } from '../../LeadsSettings';

export interface ILeadsWebPartProps {
description: string;
Expand All @@ -26,10 +27,11 @@ export interface ILeadsWebPartProps {
}

export default class LeadsWebPart extends BaseClientSideWebPart<ILeadsWebPartProps> {

private needsConfiguration: boolean;
private leadsApiUrl: string;
private connectionStatus: string;
private queryParameters: UrlQueryParameterCollection;
private view?: LeadView;

protected onInit(): Promise<void> {
if (this.properties.demo) {
Expand All @@ -40,14 +42,26 @@ export default class LeadsWebPart extends BaseClientSideWebPart<ILeadsWebPartPro
return this.getApiUrl();
}

private getLeadView(): LeadView | undefined {
const view: string = this.queryParameters.getValue('view');
const supportedViews: string[] = ['new', 'mostProbable', 'recentComments', 'requireAttention'];

if (!view || supportedViews.indexOf(view) < 0) {
return undefined;
}

return LeadView[view];
}

public render(): void {
const element: React.ReactElement<ILeadsProps> = React.createElement(
Leads,
{
demo: this.properties.demo,
httpClient: this.context.httpClient,
leadsApiUrl: this.leadsApiUrl,
needsConfiguration: this.needsConfiguration
needsConfiguration: this.needsConfiguration,
view: this.view
}
);

Expand Down Expand Up @@ -121,19 +135,20 @@ export default class LeadsWebPart extends BaseClientSideWebPart<ILeadsWebPartPro
isCollapsed: true,
groupFields: [
PropertyPaneTextField('apiUrl', {
label: 'Data Source',
value: this.leadsApiUrl || 'Not configured',
label: 'LOB API URL',
placeholder: 'Not configured',
value: this.leadsApiUrl,
disabled: true
}),
PropertyPaneLabel('spacer1', { text: '' }),
PropertyPaneButton('testConnection', {
buttonType: PropertyPaneButtonType.Primary,
disabled: this.needsConfiguration,
disabled: this.properties.demo || this.needsConfiguration,
onClick: this.testConnection,
text: 'Test connection'
}),
PropertyPaneLabel('connectionStatus', {
text: this.needsConfiguration ? 'Required tenant properties LeadsApiUrl and LeadsApiAadAppId not set' : this.connectionStatus
text: this.needsConfiguration ? 'Required tenant property LeadsApiUrl not set' : this.connectionStatus
})
]
}
Expand All @@ -157,22 +172,42 @@ export default class LeadsWebPart extends BaseClientSideWebPart<ILeadsWebPartPro
}

private getApiUrl(reRender: boolean = false): Promise<void> {
if (this.leadsApiUrl) {
this.needsConfiguration = false;
if (reRender) {
this.render();
}
return Promise.resolve();
}

return new Promise<void>((resolve: () => void, reject: (err: any) => void): void => {
this.context.spHttpClient
.get(`${this.context.pageContext.web.absoluteUrl}/_api/web/GetStorageEntity('LeadsApiUrl')`, SPHttpClient.configurations.v1)
.then((res: SPHttpClientResponse) => {
return res.json();
})
.then((res) => {
this.leadsApiUrl = res.Value;
LeadsSettings
.getLeadsApiUrl(this.context.spHttpClient, this.context.pageContext.web.absoluteUrl)
.then((leadsApiUrl: string): void => {
this.leadsApiUrl = leadsApiUrl;
this.needsConfiguration = !this.leadsApiUrl;
if (reRender) {
this.render();
}
resolve();
}, (err: any): void => {
reject(err);
});
}, () => resolve());
});
}

protected onAfterDeserialize(deserializedObject: any, dataVersion: Version): ILeadsWebPartProps {
const props: ILeadsWebPartProps = deserializedObject;
this.queryParameters = new UrlQueryParameterCollection(window.location.href);
this.view = this.getLeadView();

if (this.context.microsoftTeams && typeof this.view !== 'undefined') {
const settings: ILeadsSettings = LeadsSettings.getSettings();
if (settings) {
props.demo = settings.demo;
props.quarterlyOnly = settings.quarterlyOnly;
props.region = settings.region;
}
}

return props;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { HttpClient } from "@microsoft/sp-http";
import { LeadView } from "..";

export interface ILeadsProps {
demo: boolean;
httpClient: HttpClient;
leadsApiUrl: string;
needsConfiguration: boolean;
view?: LeadView;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { Lead } from "..";
import { Lead, LeadView } from "..";

export interface ILeadsState {
loading: boolean;
error: string | undefined;
leads: Lead[];
view: LeadView;
}

export enum LeadView {
new,
mostProbable,
recentComments,
requireAttention
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum LeadView {
new,
mostProbable,
recentComments,
requireAttention
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export class Leads extends React.Component<ILeadsProps, ILeadsState> {
loading: false,
error: undefined,
leads: [],
view: LeadView.new
view: typeof props.view !== 'undefined' ? props.view : LeadView.new
};
}

private loadLeads(view: LeadView = LeadView.new): void {
private loadLeads(view: LeadView): void {
if (this.props.needsConfiguration) {
return;
}
Expand Down Expand Up @@ -244,7 +244,13 @@ export class Leads extends React.Component<ILeadsProps, ILeadsState> {
}

public componentDidMount(): void {
this.loadLeads();
this.loadLeads(this.state.view);
}

public componentDidUpdate(prevProps: ILeadsProps, prevState: ILeadsState, snapshot?: any): void {
if (this.props.demo !== prevProps.demo) {
this.loadLeads(this.state.view);
}
}

private getCommentsForCard(comments: LeadComment[]): any {
Expand Down Expand Up @@ -315,7 +321,7 @@ export class Leads extends React.Component<ILeadsProps, ILeadsState> {
<Placeholder
iconName='Chart'
iconText='Configure your environment'
description='The required LeadsApiUrl or LeadsApiAadAppId tenant property is not configured. Please ensure that both properties are configured before using this web part.' />
description='The required LeadsApiUrl tenant property is not configured. Please configure the property before using this web part.' />
</div>;
}

Expand All @@ -336,19 +342,21 @@ export class Leads extends React.Component<ILeadsProps, ILeadsState> {
<div>
<div className={styles.viewWrapper}>
<div className={styles.title}>Leads from the Lead Management System</div>
<Dropdown
placeHolder='Select view'
className={styles.view}
options={
[
{ key: 'new', text: 'New Leads', data: LeadView.new },
{ key: 'mostProbable', text: 'Most probable', data: LeadView.mostProbable },
{ key: 'recentComments', text: 'Recently commented', data: LeadView.recentComments },
{ key: 'requireAttention', text: 'Require attention', data: LeadView.requireAttention }
]
}
selectedKey={LeadView[view]}
onChanged={this.viewChanged} />
{typeof this.props.view === 'undefined' &&
<Dropdown
placeHolder='Select view'
className={styles.view}
options={
[
{ key: 'new', text: 'New Leads', data: LeadView.new },
{ key: 'mostProbable', text: 'Most probable', data: LeadView.mostProbable },
{ key: 'recentComments', text: 'Recently commented', data: LeadView.recentComments },
{ key: 'requireAttention', text: 'Require attention', data: LeadView.requireAttention }
]
}
selectedKey={LeadView[view]}
onChanged={this.viewChanged} />
}
</div>
<div className={styles.cards}>
{
Expand Down
Loading

0 comments on commit abd9993

Please sign in to comment.