Added react-teams-lead-dashboard sample

This commit is contained in:
Paolo Pialorsi 2021-10-05 23:33:22 +02:00
parent 39ea25afc2
commit f7b86a99ac
54 changed files with 26278 additions and 0 deletions

View File

@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
release
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.12.1",
"libraryName": "lead-assist",
"libraryId": "c311a0fc-3dcb-4316-a798-fd7d8a6d5344",
"environment": "spo",
"packageManager": "npm",
"isCreatingSolution": false,
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,82 @@
# Lead Assist Dashboard
## Summary
This sample shows how to initegrate SharePoint Framework, PnP React Controls, and Microsoft Graph Toolkit in a solution available for SharePoint web parts or Microsoft Teams personal application.
![Lead Assist Dashboard](/assets/LeadAssistDashboard_overview.png)
## Used SharePoint Framework Version
![version](https://img.shields.io/npm/v/@microsoft/sp-component-base?color=green)
## Applies to
- [SharePoint Framework](https://aka.ms/spfx)
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
- [Microsoft Teams](https://www.microsoft.com/en-ww/microsoft-teams)
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
## Solution
Solution|Author(s)
--------|---------
leadAssistDashboard | [PnP](https://pnp.github.io/)
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|October 5, 2021|Initial release
## Minimal Path to Awesome
- Clone this repository
- Ensure that you are at the solution folder
- In the command-line run:
- **npm install**
- **npm run package**
- Upload the generated SPPKG file into the SharePoint App Catalog of your tenant
- Select the SPPKG in the App Catalog and click on "Sync to Teams" button
- Add the web part to a SharePoint page
- In the first run the web part will ask for the target SharePoint site URL
If needed:
- Using the control panel of the web part
- Create the SharePoint demo lists
- Populate the SharePoint demo lists
- Generate the Microsoft Graph demo data
## Features
This solution provides an example of how to implement a SharePoint Framework web part, that is also usable as a Microsoft Teams personal app, using the [SharePoint Framework React Controls](https://github.com/pnp/sp-dev-fx-controls-react/) and the [Microsoft Graph Toolkit](https://github.com/microsoftgraph/microsoft-graph-toolkit).
This web part illustrates the following concepts:
- How to use the [PnP React Controls](https://github.com/pnp/sp-dev-fx-controls-react/) such as the chart control
![Activity chart detail](/assets/ActivityChart.png)
- How to integrate the [Microsoft Graph Toolkit](https://github.com/microsoftgraph/microsoft-graph-toolkit) in a SharePoint Framework web part such as the Agenda control
![MGT Agenda control in action](/assets/AgendaControl.png)
and the Todo control
![MGT Todo control in action](/assets/TodoControl.png)
- How to execute operations on SharePoint using [PnP JS](https://github.com/pnp/pnpjs/)
## References
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
- [Building for Microsoft Teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
- [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
## 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.**
> "Sharing is Caring"

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

View File

@ -0,0 +1,73 @@
[
{
"name": "pnp-sp-dev-spfx-web-parts-react-teams-lead-dashboard",
"source": "pnp",
"title": "Lead Dashboard",
"shortDescription": "This sample shows how to use SPFx to create a Microsoft Teams dashboard personal app.",
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-teams-lead-dashboard",
"longDescription": [
"This sample shows how to use SPFx to create a Microsoft Teams dashboard personal app."
],
"creationDateTime": "2021-10-05",
"updateDateTime": "2021-10-05",
"products": [
"SharePoint",
"Office"
],
"metadata": [
{
"key": "CLIENT-SIDE-DEV",
"value": "React"
},
{
"key": "SPFX-VERSION",
"value": "1.12.0"
}
],
"thumbnails": [
{
"type": "image",
"order": 100,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-teams-lead-dashboard/assets/LeadAssistDashboard_overview.png",
"alt": "Lead Dashboard"
}
],
"authors": [
{
"gitHubAccount": "PaoloPia",
"company": "PiaSys.com",
"pictureUrl": "https://github.com/PaoloPia.png",
"name": "Paolo Pialorsi",
"twitter": "PaoloPia"
},
{
"gitHubAccount": "GuidoZam",
"company": "PiaSys.com",
"pictureUrl": "https://github.com/GuidoZam.png",
"name": "Guido Zambarda"
}
],
"references": [
{
"name": "Build your first SharePoint client-side web part",
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
},
{
"name": "Building for Microsoft Teams",
"description": "Learn how to create SPFx web parts that integrate with the Microsoft Teams context.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview"
},
{
"name": "Use Microsoft Graph in your solution",
"description": "Learn how you can leverage the Microsoft Graph API from your custom developed SPFx web parts.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis"
},
{
"name": "Publish SharePoint Framework applications to the Marketplace",
"description": "Publishing your SharePoint Framework solutions to marketplace (also known as AppSource) and to SharePoint store, which allows you to reach other organizations and let them easily install your application in their Microsoft 365 tenant.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview"
}
]
}
]

View File

@ -0,0 +1,29 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"lead-assist-dashboard-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/leadAssistDashboard/LeadAssistDashboardWebPart.js",
"manifest": "./src/webparts/leadAssistDashboard/LeadAssistDashboardWebPart.manifest.json"
}
]
},
"lead-assist-dashboard-settings-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/leadAssistDashboardSettings/LeadAssistDashboardSettingsWebPart.js",
"manifest": "./src/webparts/leadAssistDashboardSettings/LeadAssistDashboardSettingsWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"LeadAssistDashboardWebPartStrings": "lib/webparts/leadAssistDashboard/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js",
"LeadAssistDashboardSettingsWebPartStrings": "lib/webparts/leadAssistDashboardSettings/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "./release/assets/"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./release/assets/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "lead-assist",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,35 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "lead-assist-client-side-solution",
"id": "c311a0fc-3dcb-4316-a798-fd7d8a6d5344",
"version": "1.0.11.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "Tasks.ReadWrite"
},
{
"resource": "Microsoft Graph",
"scope": "Calendars.ReadWrite"
},
{
"resource": "Microsoft Graph",
"scope": "Files.ReadWrite.AppFolder"
}
],
"developer": {
"name": "PnP",
"websiteUrl": "https://pnp.github.io/",
"privacyUrl": "https://pnp.github.io/",
"termsOfUseUrl": "https://pnp.github.io/",
"mpnId": ""
}
},
"paths": {
"zippedPackage": "solution/lead-assist.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,16 @@
'use strict';
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
var result = getTasks.call(build.rig);
result.set('serve', result.get('serve-deprecated'));
return result;
};
build.initialize(require('gulp'));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
{
"name": "lead-assist",
"version": "1.0.11",
"private": true,
"main": "lib/index.js",
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"serve": "gulp serve --nobrowser",
"package": "gulp bundle --ship & gulp package-solution --ship"
},
"dependencies": {
"@microsoft/mgt-react": "^2.2.0",
"@microsoft/mgt-spfx": "^2.2.0",
"@microsoft/sp-core-library": "1.12.1",
"@microsoft/sp-lodash-subset": "1.12.1",
"@microsoft/sp-office-ui-fabric-core": "1.12.1",
"@microsoft/sp-property-pane": "1.12.1",
"@microsoft/sp-webpart-base": "1.12.1",
"@pnp/sp": "^2.7.0",
"@pnp/spfx-controls-react": "3.1.0",
"office-ui-fabric-react": "7.156.0",
"react": "16.9.0",
"react-dom": "16.9.0"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-3.7": "0.2.3",
"@microsoft/sp-build-web": "1.12.1",
"@microsoft/sp-module-interfaces": "1.12.1",
"@microsoft/sp-tslint-rules": "1.12.1",
"@microsoft/sp-webpart-workbench": "1.12.1",
"@pnp/spfx-property-controls": "^3.2.0",
"@types/react": "16.9.36",
"@types/react-dom": "16.9.8",
"@types/webpack-env": "1.13.1",
"ajv": "~5.2.2",
"gulp": "~4.0.2",
"sp-pnp-js": "^3.0.10"
}
}

View File

@ -0,0 +1,59 @@
@import './colors.module';
[data-theme='contrast'] {
:global {
.ms-Fabric {
color: $contrast-leadAssistDashboard-primaryText;
}
.ms-Button-icon {
color: $contrast-leadAssistDashboard-primaryText;
}
.ms-Overlay {
background-color: $contrast-leadAssistDashboard-overlay;
}
.ms-Panel-main {
background-color: $contrast-leadAssistDashboard-surfaceBackground;
border-left-color: $contrast-leadAssistDashboard-panelBorder;
border-right-color: $contrast-leadAssistDashboard-panelBorder;
.ms-Panel-headerText {
color: $contrast-leadAssistDashboard-primaryText;
}
}
.spPropertyPaneContainer {
background-color: $contrast-leadAssistDashboard-white;
[class^="propertyPane_"] {
background-color: $contrast-leadAssistDashboard-white;
border-left-color: $contrast-leadAssistDashboard-panelBorder;
[class^="propertyPanePageTitle_"],
[class^="propertyPanePageDescription_"],
[class^="propertyPaneGroupHeaderNoAccordion_"] {
color: $contrast-leadAssistDashboard-primaryText;
}
.ms-Button--icon {
&:hover {
background-color: transparent;
}
}
}
}
.ms-Label {
color: $contrast-leadAssistDashboard-primaryText;
}
.ms-TextField {
.ms-TextField-fieldGroup {
background-color: $contrast-leadAssistDashboard-inputBackground;
color: $contrast-leadAssistDashboard-primaryText;
border-color: $contrast-leadAssistDashboard-inputBorder;
.ms-TextField-field {
color: $contrast-leadAssistDashboard-primaryText;
}
&:hover {
border-color: $contrast-leadAssistDashboard-inputBorderHovered;
}
}
}
}
}

View File

@ -0,0 +1,59 @@
@import './colors.module';
[data-theme='dark'] {
:global {
.ms-Fabric {
color: $dark-leadAssistDashboard-primaryText;
}
.ms-Button-icon {
color: $dark-leadAssistDashboard-primaryText;
}
.ms-Overlay {
background-color: $dark-leadAssistDashboard-overlay;
}
.ms-Panel-main {
background-color: $dark-leadAssistDashboard-surfaceBackground;
border-left-color: $dark-leadAssistDashboard-panelBorder;
border-right-color: $dark-leadAssistDashboard-panelBorder;
.ms-Panel-headerText {
color: $dark-leadAssistDashboard-primaryText;
}
}
.spPropertyPaneContainer {
background-color: $dark-leadAssistDashboard-white;
[class^="propertyPane_"] {
background-color: $dark-leadAssistDashboard-white;
border-left-color: $dark-leadAssistDashboard-panelBorder;
[class^="propertyPanePageTitle_"],
[class^="propertyPanePageDescription_"],
[class^="propertyPaneGroupHeaderNoAccordion_"] {
color: $dark-leadAssistDashboard-primaryText;
}
.ms-Button--icon {
&:hover {
background-color: transparent;
}
}
}
}
.ms-Label {
color: $dark-leadAssistDashboard-primaryText;
}
.ms-TextField {
.ms-TextField-fieldGroup {
background-color: $dark-leadAssistDashboard-inputBackground;
color: $dark-leadAssistDashboard-primaryText;
border-color: $dark-leadAssistDashboard-inputBorder;
.ms-TextField-field {
color: $dark-leadAssistDashboard-primaryText;
}
&:hover {
border-color: $dark-leadAssistDashboard-inputBorderHovered;
}
}
}
}
}

View File

@ -0,0 +1,61 @@
@import './colors.module';
[data-theme='default'] {
:global {
.ms-Fabric {
color: $default-leadAssistDashboard-primaryText;
}
.ms-Button-icon {
color: $default-leadAssistDashboard-primaryText;
}
.ms-Overlay {
background-color: $default-leadAssistDashboard-overlay;
}
.ms-Panel-main {
background-color: $default-leadAssistDashboard-surfaceBackground;
border-left-color: $default-leadAssistDashboard-panelBorder;
border-right-color: $default-leadAssistDashboard-panelBorder;
.ms-Panel-headerText {
color: $default-leadAssistDashboard-primaryText;
}
}
// Property Pane
.spPropertyPaneContainer {
background-color: $default-leadAssistDashboard-white;
[class^="propertyPane_"] {
background-color: $default-leadAssistDashboard-white;
border-left-color: $default-leadAssistDashboard-panelBorder;
[class^="propertyPanePageTitle_"],
[class^="propertyPanePageDescription_"],
[class^="propertyPaneGroupHeaderNoAccordion_"] {
color: $default-leadAssistDashboard-primaryText;
}
.ms-Button--icon {
&:hover {
background-color: transparent;
}
}
}
}
// Text Field
.ms-Label {
color: $default-leadAssistDashboard-primaryText;
}
.ms-TextField {
.ms-TextField-fieldGroup {
background-color: $default-leadAssistDashboard-inputBackground;
color: $default-leadAssistDashboard-primaryText;
border-color: $default-leadAssistDashboard-inputBorder;
.ms-TextField-field {
color: $default-leadAssistDashboard-primaryText;
}
&:hover {
border-color: $default-leadAssistDashboard-inputBorderHovered;
}
}
}
}
}

View File

@ -0,0 +1,91 @@
//SharePoint
$leadAssistDashboard-background: "[theme:white, default:#fff]";
$leadAssistDashboard-color: "[theme:primaryText, default:#333]";
$leadAssistDashboard-buttonBackground: "[theme:themePrimary, default:#0078d4]";
$leadAssistDashboard-buttonColor: "[theme:white, default:#fff]";
$leadAssistDashboard-callRGBColor: rgb(101, 94, 134);
$leadAssistDashboard-emailRGBColor: rgb(237, 208, 149);
$leadAssistDashboard-textRGBColor: rgb(207, 157, 188);
$leadAssistDashboard-eventColor: #0078d4;
$leadAssistDashboard-eventTeamsColor: #6264a7;
$leadAssistDashboard-firstComponent-background: "[theme:white, default:#fff]";
$leadAssistDashboard-firstComponent-color: "[theme:primaryText, default:#333]";
$leadAssistDashboard-firstComponentButton-background: "[theme:themePrimary, default:#0078d4]";
$leadAssistDashboard-firstComponentButton-color: "[theme:white, default:#fff]";
$leadAssistDashboard-white: "[theme:white, default: #fff]"; // property pane background
$leadAssistDashboard-inputBackground: "[theme:inputBackground, default:#fff]"; //input background
$leadAssistDashboard-inputBorder: "[theme:inputBorder, default:#a6a6a6]"; // input border
$leadAssistDashboard-inputBorderHovered: "[theme:inputBorderHovered, default:#333333]"; // input border hovered
$leadAssistDashboard-overlay: "[theme:whiteTranslucent40, default:rgba(255, 255,255, 0.4)]";
$leadAssistDashboard-surfaceBackground: "[theme:white, default:#fff]";
$leadAssistDashboard-primaryText: "[theme:primaryText, default:#333]";
$leadAssistDashboard-panelBorder: "[theme: neutralLight, default: #eaeaea]";
// default theme
$default-leadAssistDashboard-background: #f3f2f1;
$default-leadAssistDashboard-color: #252423;
$default-leadAssistDashboard-buttonBackground: #6264a7;
$default-leadAssistDashboard-buttonColor: #f3f2f1;
$default-leadAssistDashboard-callRGBColor: rgb(101, 94, 134);
$default-leadAssistDashboard-emailRGBColor: rgb(237, 208, 149);
$default-leadAssistDashboard-textRGBColor: rgb(207, 157, 188);
$default-leadAssistDashboard-eventColor: #0078d4;
$default-leadAssistDashboard-eventTeamsColor: #6264a7;
$default-leadAssistDashboard-firstComponent-background: #f3f2f1;
$default-leadAssistDashboard-firstComponent-color: #252423;
$default-leadAssistDashboard-firstComponentButton-background: #6264a7;
$default-leadAssistDashboard-firstComponentButton-color: #f3f2f1;
$default-leadAssistDashboard-white: #f3f2f1; // property pane background
$default-leadAssistDashboard-inputBackground: #fff;
$default-leadAssistDashboard-inputBorder: #b5b4b2;
$default-leadAssistDashboard-inputBorderHovered: #252423;
$default-leadAssistDashboard-overlay: rgba(255, 255, 255, 0.4);
$default-leadAssistDashboard-surfaceBackground: #f3f2f1;
$default-leadAssistDashboard-primaryText: #252423;
$default-leadAssistDashboard-panelBorder: #dedddc;
// dark theme
$dark-leadAssistDashboard-background: #2d2c2c;
$dark-leadAssistDashboard-color: #ffffff;
$dark-leadAssistDashboard-buttonBackground: #6264a7;
$dark-leadAssistDashboard-buttonColor: #2d2c2c;
$dark-leadAssistDashboard-callRGBColor: rgb(101, 94, 134);
$dark-leadAssistDashboard-emailRGBColor: rgb(237, 208, 149);
$dark-leadAssistDashboard-textRGBColor: rgb(207, 157, 188);
$dark-leadAssistDashboard-eventColor: #0078d4;
$dark-leadAssistDashboard-eventTeamsColor: #6264a7;
$dark-leadAssistDashboard-firstComponent-background: #2d2c2c;
$dark-leadAssistDashboard-firstComponent-color: #ffffff;
$dark-leadAssistDashboard-firstComponentButton-background: #6264a7;
$dark-leadAssistDashboard-firstComponentButton-color: #2d2c2c;
$dark-leadAssistDashboard-white: #2d2c2c; // property pane background
$dark-leadAssistDashboard-inputBackground: #000;
$dark-leadAssistDashboard-inputBorder: #c8c8c8;
$dark-leadAssistDashboard-inputBorderHovered: #ffffff;
$dark-leadAssistDashboard-overlay: rgba(37, 36, 35, 0.75);
$dark-leadAssistDashboard-surfaceBackground: #2d2c2c;
$dark-leadAssistDashboard-primaryText: #ffffff;
$dark-leadAssistDashboard-panelBorder: #4c4b4b;
// contrast theme
$contrast-leadAssistDashboard-background: #000000;
$contrast-leadAssistDashboard-color: #ffffff;
$contrast-leadAssistDashboard-buttonBackground: #6264a7;
$contrast-leadAssistDashboard-buttonColor: #000000;
$contrast-leadAssistDashboard-callRGBColor: rgb(101, 94, 134);
$contrast-leadAssistDashboard-emailRGBColor: rgb(237, 208, 149);
$contrast-leadAssistDashboard-textRGBColor: rgb(207, 157, 188);
$contrast-leadAssistDashboard-eventColor: #0078d4;
$contrast-leadAssistDashboard-eventTeamsColor: #6264a7;
$contrast-leadAssistDashboard-firstComponent-background: #000000;
$contrast-leadAssistDashboard-firstComponent-color: #ffffff;
$contrast-leadAssistDashboard-firstComponentButton-background: #6264a7;
$contrast-leadAssistDashboard-firstComponentButton-color: #000000;
$contrast-leadAssistDashboard-white: #000000; // property pane background
$contrast-leadAssistDashboard-inputBackground: #000;
$contrast-leadAssistDashboard-inputBorder: #c8c8c8;
$contrast-leadAssistDashboard-inputBorderHovered: #ffffff;
$contrast-leadAssistDashboard-overlay: rgba(37, 36, 35, 0.75);
$contrast-leadAssistDashboard-surfaceBackground: #000000;
$contrast-leadAssistDashboard-primaryText: #ffffff;
$contrast-leadAssistDashboard-panelBorder: #4c4b4b;

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,370 @@
import * as strings from "LeadAssistDashboardWebPartStrings";
import { Providers } from "@microsoft/mgt-spfx";
import { IList, sp } from "@pnp/sp/presets/all";
import { IListField } from "./IListField";
import { IValueListItem } from "./IValueListItem";
import { ISaleListItem } from "./ISaleListItem";
export default class DataService {
// Lists names
public static ActivityCallsListName: string = strings.ActivityCallsListName;
public static ActivityEmailsListName: string = strings.ActivityEmailsListName;
public static ActivityTextsListName: string = strings.ActivityTextsListName;
public static ProgressListName: string = strings.ProgressListName;
public static RecentlyDoneSalesContractsListName: string = strings.RecentlyDoneSalesContractsListName;
// Fields titles
public static FieldTitleName: string = "Title";
public static FieldValueName: string = "DemoValue";
public static FieldDescriptionName: string = "DemoDescription";
public static FieldMonthName: string = "DemoMonth";
public static FieldGroupName: string = "Demo Group";
// Fields types
public static FieldTypeText: string = "SP.FieldText";
public static FieldTypeTextKindId: number = 2;
public static FieldTypeNumber: string = "SP.FieldNumber";
public static FieldTypeNumberKindId: number = 9;
public static MonthNames: string[] = [ strings.January, strings.February, strings.March, strings.April, strings.May, strings.June, strings.July ];
// Value field definition
private static ValueField: IListField = {
fieldName: DataService.FieldValueName,
fieldType: DataService.FieldTypeNumber,
fieldTypeKindId: DataService.FieldTypeNumberKindId
};
// Description field definition
private static DescriptionField: IListField = {
fieldName: DataService.FieldDescriptionName,
fieldType: DataService.FieldTypeText,
fieldTypeKindId: DataService.FieldTypeTextKindId
};
// Month field definition
private static MonthField: IListField = {
fieldName: DataService.FieldMonthName,
fieldType: DataService.FieldTypeText,
fieldTypeKindId: DataService.FieldTypeTextKindId
};
/**
* Format the specific date in a short locale string
* @param date The date to be formatted
* @returns The formatted date in string format
*/
public static getTime(date: Date): string {
const timeOptions = {
timeStyle: 'short',
hour12: true
};
return date.toLocaleTimeString([], timeOptions);
}
/**
* Format the specific date in a short locale string
* @param date The date to be formatted
* @returns The formatted date in string format
*/
public static getDate(date: Date): string {
return date.toLocaleDateString([], { day: 'numeric', month: 'short' });
}
/**
* Generate an array of number for demo data
* @param length Length of the numeric array
* @returns Numeric array of demo data
*/
public static generateNumericDemoData(length: number): number[] {
let demoData = [...Array(length)].map(() => {
let n = Math.floor(Math.random() * 9);
return n;
});
return demoData;
}
/**
* Generate the demo fields and lists
*/
public static async generateDemoLists(): Promise<void> {
if (confirm(strings.ConfirmCreateDemoLists) == true) {
// Ensure fields
await this.ensureWebField(DataService.FieldValueName, DataService.FieldTypeNumber, DataService.FieldTypeNumberKindId);
await this.ensureWebField(DataService.FieldDescriptionName, DataService.FieldTypeText, DataService.FieldTypeTextKindId);
await this.ensureWebField(DataService.FieldMonthName, DataService.FieldTypeText, DataService.FieldTypeTextKindId);
// Ensure lists
await this.ensureList(DataService.ActivityCallsListName, [this.ValueField]);
await this.ensureList(DataService.ActivityEmailsListName, [this.ValueField]);
await this.ensureList(DataService.ActivityTextsListName, [this.ValueField]);
await this.ensureList(DataService.ProgressListName, [this.ValueField]);
await this.ensureList(DataService.RecentlyDoneSalesContractsListName, [this.DescriptionField]);
alert(strings.DemoListsCreated);
}
}
/**
* Generate the demo data for the SharePoint lists
*/
public static async generateListsDemoData(): Promise<void> {
if (confirm(strings.ConfirmAddDemoData) == true) {
// Get all the lists references
const activityCallsList = await sp.web.lists.getByTitle(DataService.ActivityCallsListName);
const activityEmailsList = await sp.web.lists.getByTitle(DataService.ActivityEmailsListName);
const activityTextsList = await sp.web.lists.getByTitle(DataService.ActivityTextsListName);
const progressList = await sp.web.lists.getByTitle(DataService.ProgressListName);
const recentContractsList = await sp.web.lists.getByTitle(DataService.RecentlyDoneSalesContractsListName);
// Generate sales products list demo data
DataService.generateNumericDemoData(7).forEach((p, i) => {
activityCallsList.items.add({
Title: strings.DemoActivityCallTitle + " " + i,
DemoValue: p * 10
});
});
// Generate sales services list demo data
DataService.generateNumericDemoData(7).forEach((p, i) => {
activityEmailsList.items.add({
Title: strings.DemoActivityEmailTitle + " " + i,
DemoValue: p * 10
});
});
// Generate activity text list demo data
DataService.generateNumericDemoData(7).forEach((p, i) => {
activityTextsList.items.add({
Title: strings.DemoActivityTextTitle + " " + i,
DemoValue: p * 10
});
});
// Generate progress list demo data
DataService.generateNumericDemoData(9).forEach(p => {
progressList.items.add({
Title: p.toString(),
DemoValue: p
});
});
// Generate recently list demo data
[...Array(5)].map((v, i) => {
return {
title: strings.DemoDocumentTitle + " " + i,
description: strings.DemoDocumentDescription + " " + i
};
}).forEach(r => {
recentContractsList.items.add({
Title: r.title,
DemoDescription: r.description
});
});
alert(strings.DemoDataGenerated);
}
}
/**
* Generate demo data using Graph
*/
public static async generateGraphDemoData(): Promise<void> {
if (confirm(strings.ConfirmAddGraphDemoData) == true) {
let provider = Providers.globalProvider;
if (provider) {
// Get the Graph client
let graphClient = provider.graph.client;
// Add new task
let taskLists = await graphClient.api("me/todo/lists").get();
// Simple task
await graphClient.api(`me/todo/lists/${taskLists.value[0].id}/tasks`).create({
"title": strings.DemoEventTitle,
"linkedResources": [
{
"webUrl": "https://aka.ms/m365pnp",
"applicationName": strings.DemoApplicationName,
"displayName": strings.DemoDisplayName
}
]
});
// Task with due date
let taskDate = new Date();
taskDate.setDate(taskDate.getDate() + 1);
await graphClient.api(`me/todo/lists/${taskLists.value[0].id}/tasks`).create({
"title": strings.DemoTaskWithDateTitle,
"dueDateTime": {
"dateTime": taskDate.toISOString(),
"timeZone": "UTC"
},
"linkedResources":[
{
"webUrl": "https://aka.ms/m365pnp",
"applicationName": strings.DemoApplicationName,
"displayName": strings.DemoDisplayName
}
]
});
// Start and end date for events
let startDate = new Date();
let endDate = new Date();
// Set the start and end date to tomorrow
startDate.setDate(startDate.getDate() + 1);
endDate.setDate(endDate.getDate() + 1);
// Add one hour to the end date
endDate.setHours(endDate.getHours() + 1);
// Event with location
await graphClient.api("me/events").create({
"subject": strings.DemoEventTitle,
"body": {
"contentType": "HTML",
"content": strings.DemoEventContent
},
"start": {
"dateTime": startDate.toISOString(),
"timeZone": "UTC"
},
"end": {
"dateTime": endDate.toISOString(),
"timeZone": "UTC"
},
"location":{
"displayName": strings.DemoEventLocation
}
});
// Change date for the next event
startDate.setDate(startDate.getDate() + 1);
endDate.setDate(endDate.getDate() + 1);
// Online event
await graphClient.api("me/events").create({
"subject": strings.DemoEventOnlineTitle,
"body": {
"contentType": "HTML",
"content": strings.DemoEventOnlineContent
},
"start": {
"dateTime": startDate.toISOString(),
"timeZone": "Pacific Standard Time"
},
"end": {
"dateTime": endDate.toISOString(),
"timeZone": "Pacific Standard Time"
},
"isOnlineMeeting": "true"
});
}
alert(strings.GraphDemoDataGenerated);
}
}
/**
* Delete the SharePoint lists
*/
public static async deleteSharePointDemoLists(): Promise<void> {
if (confirm(strings.ConfirmDeleteSharePointDemoLists) == true) {
// Get all the lists references
const activityCallsList = await sp.web.lists.getByTitle(DataService.ActivityCallsListName);
const activityEmailsList = await sp.web.lists.getByTitle(DataService.ActivityEmailsListName);
const activityTextsList = await sp.web.lists.getByTitle(DataService.ActivityTextsListName);
const progressList = await sp.web.lists.getByTitle(DataService.ProgressListName);
const recentContractsList = await sp.web.lists.getByTitle(DataService.RecentlyDoneSalesContractsListName);
// Delete the lists
await activityCallsList.delete();
await activityEmailsList.delete();
await activityTextsList.delete();
await progressList.delete();
await recentContractsList.delete();
alert(strings.SharePointDemoListsDeleted);
}
}
/**
* Get items with values from a specific SharePoint list
* @param listTitle Title of the list
* @param mappingFunction Function to map every items of the list
* @returns An array of the mapped items
*/
public static async getItemsWithValueFromList(listTitle: string, mappingFunction: (value: any, index: number) => IValueListItem): Promise<IValueListItem[]> {
return await this.getItemsFromList(listTitle, mappingFunction);
}
/**
* Get sales items from a specific SharePoint list
* @param listTitle Title of the list
* @param mappingFunction Function to map every items of the list
* @returns An array of the mapped items
*/
public static async getSalesItemsFromList(listTitle: string, mappingFunction: (value: any, index: number) => ISaleListItem): Promise<ISaleListItem[]> {
return await this.getItemsFromList(listTitle, mappingFunction);
}
/**
* Get items from a specific SharePoint list
* @param listTitle Title of the list
* @param mappingFunction Function to map every items of the list
* @returns An array of the mapped items
*/
private static async getItemsFromList<T>(listTitle: string, mappingFunction: (value: T, index: number) => T): Promise<T[]> {
// Get the items from the list
const items = await sp.web.lists.getByTitle(listTitle).items.getAll();
// Map the items with the specified function
const mappedItems = items.map((v, i) => mappingFunction(v, i));
return mappedItems;
}
/**
* Ensure that a field on a SharePoint site exists, otherwise create it
* @param fieldName Name of the field
* @param fieldType Type of the field
* @param typeKindId Type kind id of the field
*/
private static async ensureWebField(fieldName: string, fieldType: string, typeKindId: number) {
var existingField = (await sp.web.fields.get()).filter(f => f.StaticName == fieldName);
// If the field has not been added yet create it
if (existingField && existingField.length == 0) {
await sp.web.fields.add(fieldName, fieldType, { FieldTypeKind: typeKindId, Group: DataService.FieldGroupName });
}
}
/**
* Add a specific field to the target SharePoint list
* @param targetList Name of the target SharePoint list
* @param fieldName Name of the field
* @param fieldType Type of the field
* @param typeKindId Type kind id of the field
*/
private static async addFieldToList(targetList: IList, fieldName: string, fieldType: string, fieldTypeKindId: number) {
await targetList.fields.add(fieldName, fieldType, { FieldTypeKind: fieldTypeKindId, Group: DataService.FieldGroupName });
}
/**
* Ensure that a SharePoint list exists, otherwise create it
* @param listName Name of the SharePoint list
* @param fieldsToAdd Fields to add to the SharePoint list
*/
private static async ensureList(listName: string, fieldsToAdd: IListField[]) {
// Ensure the existence of the demo list
const listExists = await sp.web.lists.ensure(listName);
// If the list exists and there are fields specified
if (listExists.created == true && fieldsToAdd != undefined) {
// Cycle through fields to add
for (let i = 0; i < fieldsToAdd.length; i++) {
await this.addFieldToList(listExists.list, fieldsToAdd[i].fieldName, fieldsToAdd[i].fieldType, fieldsToAdd[i].fieldTypeKindId);
}
}
}
}

View File

@ -0,0 +1,5 @@
export interface IListField {
fieldName: string;
fieldType: string;
fieldTypeKindId: number;
}

View File

@ -0,0 +1,5 @@
export interface ISaleListItem {
id: number;
title: string;
description: string;
}

View File

@ -0,0 +1,3 @@
export interface IValueListItem {
value: number;
}

View File

@ -0,0 +1,47 @@
import { HttpClient, MSGraphClient } from "@microsoft/sp-http";
export default class SettingsService {
/**
* Get the application settings from drive
* @param graphClient Graph client to be used for the request
* @param httpClient Http client to be used for the request
* @returns Object representing the JSON settings file
*/
public static async getSettings(graphClient: MSGraphClient, httpClient: HttpClient): Promise<any> {
// Get approot files
const approotFiles = await graphClient
.api('/me/drive/special/approot/children')
.version("v1.0")
.get();
// If any it means that a settings.json file exists
if (approotFiles.value.length > 0){
// Get the settings.json file download URL
const downloadUrl = approotFiles.value[0]["@microsoft.graph.downloadUrl"];
// Get the settings.json file
const settingsResponse = await httpClient.get(downloadUrl, HttpClient.configurations.v1);
const settings = await settingsResponse.json();
return settings;
}
return undefined;
}
/**
* Save the specified settings to drive
* @param graphClient Graph client to be used for the request
* @param settings Object representing the settings to be saved on the JSON settings file
*/
public static async saveSiteUrl(graphClient: MSGraphClient, settings: any) {
// Save the settings in the application dedicated folder
await graphClient
.api('/me/drive/special/approot:/settings.json:/content')
.version("v1.0")
.header('content-type', 'text/plain')
.put(JSON.stringify(settings));
location.reload();
}
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "29c7e411-c4ec-4592-8b24-dbfd8bd1e1ca",
"alias": "LeadAssistDashboardWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportsFullBleed": true,
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp"],
"canUpdateConfiguration": true,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "Lead Assist Dashboard" },
"description": { "default": "Lead Assist Dashboard" },
"officeFabricIconFontName": "BIDashboard",
"properties": {
}
}]
}

View File

@ -0,0 +1,165 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneButton,
PropertyPaneButtonType
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import * as strings from 'LeadAssistDashboardWebPartStrings';
import LeadAssistDashboard from './components/LeadAssistDashboard';
import { ILeadAssistDashboardProps } from './components/ILeadAssistDashboardProps';
import { Providers, SharePointProvider } from '@microsoft/mgt-spfx';
import { sp } from "@pnp/sp/presets/all";
import DataService from '../../services/DataService';
import { MSGraphClient } from "@microsoft/sp-http";
import SettingsService from '../../services/SettingsService';
export interface ILeadAssistDashboardWebPartProps {
}
export default class LeadAssistDashboardWebPart extends BaseClientSideWebPart<ILeadAssistDashboardWebPartProps> {
private isTeamsContext: boolean;
private siteUrl: string;
private graphClient: MSGraphClient;
protected async onInit() {
// Determine if we are in the Teams context
this.isTeamsContext = !!this.context.sdks.microsoftTeams;
if (this.isTeamsContext) {
const teamsContext = this.context.sdks.microsoftTeams.context;
this.applyTheme(teamsContext.theme || 'default');
this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler(this.applyTheme);
}
// Create the Microsoft Graph client
this.graphClient = await this.context.msGraphClientFactory.getClient();
// Get the settings
const settings = await SettingsService.getSettings(this.graphClient, this.context.httpClient);
// If there are settings specified
if (settings) {
// Get the site URL
this.siteUrl = settings.siteUrl;
}
// If no global provider has been specified
if (!Providers.globalProvider) {
// Create a global SharePoint provider
Providers.globalProvider = new SharePointProvider(this.context);
}
// If site url has been specified
if (this.siteUrl && this.siteUrl.length > 0) {
// Setup the SharePoint client
sp.setup({
spfxContext: this.context,
sp: {
baseUrl: this.siteUrl
}
});
}
}
private buttonsAreDisabled() {
return !this.siteUrl || this.siteUrl.length == 0;
}
private applyTheme = (theme: string): void => {
this.context.domElement.setAttribute('data-theme', theme);
document.body.setAttribute('data-theme', theme);
}
public render(): void {
const element: React.ReactElement<ILeadAssistDashboardProps> = React.createElement(
LeadAssistDashboard,
{
isTeamsContext: this.isTeamsContext,
siteUrl: this.siteUrl,
graphClient: this.graphClient
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
private async generateSharePointDemoListsClick(): Promise<void> {
await DataService.generateDemoLists();
}
private async generateSharePointDemoDataClick(): Promise<void> {
await DataService.generateListsDemoData();
}
private async generateGraphDemoDataClick(): Promise<void> {
await DataService.generateGraphDemoData();
}
private async deleteSharePointDemoListsClick(): Promise<void> {
await DataService.deleteSharePointDemoLists();
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.GenerateDemoDataGroupName,
groupFields: [
PropertyPaneButton('GenerateSharePointDemoLists',
{
text: strings.GenerateSharePointDemoListsButton,
buttonType: PropertyPaneButtonType.Normal,
onClick: this.generateSharePointDemoListsClick.bind(this),
disabled: this.buttonsAreDisabled()
}),
PropertyPaneButton('GenerateSharePointDemoData',
{
text: strings.GenerateSharePointDemoDataButton,
buttonType: PropertyPaneButtonType.Normal,
onClick: this.generateSharePointDemoDataClick.bind(this),
disabled: this.buttonsAreDisabled()
}),
PropertyPaneButton('GenerateGraphDemoData',
{
text: strings.GenerateGraphDemoDataButton,
buttonType: PropertyPaneButtonType.Normal,
onClick: this.generateGraphDemoDataClick.bind(this),
disabled: this.buttonsAreDisabled()
})
]
},
{
groupName: strings.CleanDemoDataGroupName,
groupFields: [
PropertyPaneButton('DeleteSharePointDemoLists',
{
text: strings.DeleteDemoDataButton,
buttonType: PropertyPaneButtonType.Normal,
onClick: this.deleteSharePointDemoListsClick.bind(this),
disabled: this.buttonsAreDisabled()
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,64 @@
import * as React from 'react';
import * as strings from 'LeadAssistDashboardWebPartStrings';
import styles from './LeadAssistDashboard.module.scss';
import { MgtTemplateProps } from '@microsoft/mgt-react/dist/es6/spfx';
import { Separator, Spinner } from 'office-ui-fabric-react';
import DataService from '../../../services/DataService';
export default class CustomAgendaTemplate {
/**
* Template to display a single event
* @param props
* @returns The element that display a single event
*/
public static eventTemplate = (props: MgtTemplateProps): JSX.Element => {
const { event } = props.dataContext;
let eventLocation: string = "";
let isTeamsMeeting: boolean = false;
if (event.isOnlineMeeting == true) {
eventLocation = strings.MicrosoftTeams;
isTeamsMeeting = true;
}
else {
if (event.location) {
if (event.location.displayName && event.location.displayName.length > 0) {
eventLocation = event.location.displayName;
}
}
}
const startDate: Date = new Date(event.start.dateTime);
const endDate: Date = new Date(event.end.dateTime);
return <div className={styles.event}>
<div className={((isTeamsMeeting == true) ? styles.teamsEventBorder : styles.eventBorder)}>
<div className={styles.leftPadding}>
<div className={styles.eventTitle}>{event.subject}</div>
<div>{DataService.getTime(startDate)} - {DataService.getTime(endDate)}</div>
<div>{eventLocation}</div>
</div>
</div>
<Separator />
</div>;
}
/**
* Template to show when events are being loaded
* @param props
* @returns The element to display the loading state
*/
public static loadingTemplate = (props: MgtTemplateProps): JSX.Element => {
return <Spinner label="Loading..."></Spinner>;
}
/**
* Template to show when there are no events available
* @param props
* @returns The element to display that no event is available
*/
public static noDataTemplate = (props: MgtTemplateProps): JSX.Element => {
return <div>{strings.NoEventFound}</div>;
}
}

View File

@ -0,0 +1,38 @@
import * as React from 'react';
import styles from './LeadAssistDashboard.module.scss';
import { MgtTemplateProps } from '@microsoft/mgt-react/dist/es6/spfx';
import { Separator } from 'office-ui-fabric-react';
import DataService from '../../../services/DataService';
export default class CustomTodoTemplate {
/**
* Template to display a single todo
* @param props
* @returns The element that display a single todo
*/
public static todoTemplate = (props: MgtTemplateProps): JSX.Element => {
const { task } = props.dataContext;
const title: string = task.title;
const dueDateTime: Date = (task.dueDateTime) ? new Date(task.dueDateTime.dateTime) : undefined;
let result: JSX.Element = <div></div>;
// If task is not completed
if (task.status != "completed") {
// Set the template
result = <div className={styles.task}>
<div>
<div className={styles.leftPadding}>
{dueDateTime &&
<div className={styles.taskDateTime}>{DataService.getDate(dueDateTime)}, {DataService.getTime(dueDateTime)}</div>}
<div className={styles.taskTitle}>{title}</div>
</div>
</div>
<Separator />
</div>;
}
return result;
}
}

View File

@ -0,0 +1,6 @@
import { MSGraphClient } from "@microsoft/sp-http";
export interface ILeadAssistDashboardProps {
isTeamsContext: boolean;
siteUrl: string;
graphClient: MSGraphClient;
}

View File

@ -0,0 +1,13 @@
import { ISaleListItem } from "../../../services/ISaleListItem";
import { IValueListItem } from "../../../services/IValueListItem";
export interface ILeadAssistDashboardState {
activityCallItems: IValueListItem[];
activityEmailItems: IValueListItem[];
activityTextItems: IValueListItem[];
progressItems: IValueListItem[];
recentlyDoneSalesContractItems: ISaleListItem[];
isLoading: boolean;
listsAreEmpty: boolean;
siteUrl: string;
}

View File

@ -0,0 +1,273 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
@import '../../../common/colors.module.scss';
@import '../../../common/Global.dark.module.scss';
@import '../../../common/Global.default.module.scss';
@import '../../../common/Global.contrast.module.scss';
:export {
callRGBColor: $leadAssistDashboard-callRGBColor;
emailRGBColor: $leadAssistDashboard-emailRGBColor;
textRGBColor: $leadAssistDashboard-textRGBColor;
}
.leadAssistDashboard {
max-width: 1648px;
.dot {
height: 10px;
width: 10px;
border-radius: 50%;
display: inline-block;
margin: 2px;
}
.chartCallDot {
@extend .dot;
background-color: $leadAssistDashboard-callRGBColor;
}
.chartEmailDot {
@extend .dot;
background-color: $leadAssistDashboard-emailRGBColor;
}
.chartTextDot {
@extend .dot;
background-color: $leadAssistDashboard-textRGBColor;
}
.chartNumber {
font-size: large;
}
.tip {
padding: 8px;
}
.grid {
@include ms-Grid;
}
.row {
@include ms-Grid-row;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.smallColumn {
@extend .column;
max-width: 100px;
}
.smallColumnTeams {
@extend .column;
max-width: 180px;
}
.chartColumn {
@extend .column;
max-width: 100px;
}
.chartColumnTeams {
@extend .column;
max-width: 200px;
margin-left: -10px;
}
.event {
margin-bottom: 2px;
}
.eventBorder {
border-left: 4px solid $leadAssistDashboard-eventColor;
}
.teamsEventBorder{
@extend .eventBorder;
border-left: 4px solid $leadAssistDashboard-eventTeamsColor;
}
.leftPadding{
padding-left: 9px;
}
.eventTitle {
font-weight: bold;
}
.task {
margin-bottom: 2px;
}
.taskTitle {
font-weight: bold;
}
.taskDateTime {
font-weight: lighter;
font-size: smaller;
}
.paddedContainer {
padding: 15px;
}
.padding5 {
padding: 5px;
}
.loader {
padding: 30px;
}
.centeredContainer {
margin: 0;
position: absolute;
top: 50%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
.progressDescription {
text-align: center;
}
.marginLeft {
margin-left: -30px;
}
}
[data-theme='default'] {
:export {
callRGBColor: $default-leadAssistDashboard-callRGBColor;
emailRGBColor: $default-leadAssistDashboard-emailRGBColor;
textRGBColor: $default-leadAssistDashboard-textRGBColor;
}
.leadAssistDashboard {
background: $default-leadAssistDashboard-background;
color: $default-leadAssistDashboard-color;
.button {
background: $default-leadAssistDashboard-buttonBackground;
color: $default-leadAssistDashboard-buttonColor;
}
.chartCallDot {
@extend .dot;
background-color: $default-leadAssistDashboard-callRGBColor;
}
.chartEmailDot {
@extend .dot;
background-color: $default-leadAssistDashboard-emailRGBColor;
}
.chartTextDot {
@extend .dot;
background-color: $default-leadAssistDashboard-textRGBColor;
}
.eventBorder {
border-left: 4px solid $default-leadAssistDashboard-eventColor;
}
.teamsEventBorder{
@extend .eventBorder;
border-left: 4px solid $default-leadAssistDashboard-eventTeamsColor;
}
}
}
[data-theme='dark'] {
:export {
callRGBColor: $dark-leadAssistDashboard-callRGBColor;
emailRGBColor: $dark-leadAssistDashboard-emailRGBColor;
textRGBColor: $dark-leadAssistDashboard-textRGBColor;
}
.leadAssistDashboard {
background: $dark-leadAssistDashboard-background;
color: $dark-leadAssistDashboard-color;
.button {
background: $dark-leadAssistDashboard-buttonBackground;
color: $dark-leadAssistDashboard-buttonColor;
}
.chartCallDot {
@extend .dot;
background-color: $dark-leadAssistDashboard-callRGBColor;
}
.chartEmailDot {
@extend .dot;
background-color: $dark-leadAssistDashboard-emailRGBColor;
}
.chartTextDot {
@extend .dot;
background-color: $dark-leadAssistDashboard-textRGBColor;
}
.eventBorder {
border-left: 4px solid $dark-leadAssistDashboard-eventColor;
}
.teamsEventBorder{
@extend .eventBorder;
border-left: 4px solid $dark-leadAssistDashboard-eventTeamsColor;
}
}
}
[data-theme='contrast'] {
:export {
callRGBColor: $contrast-leadAssistDashboard-callRGBColor;
emailRGBColor: $contrast-leadAssistDashboard-emailRGBColor;
textRGBColor: $contrast-leadAssistDashboard-textRGBColor;
}
.leadAssistDashboard {
background: $contrast-leadAssistDashboard-background;
color: $contrast-leadAssistDashboard-color;
.button {
background: $contrast-leadAssistDashboard-buttonBackground;
color: $contrast-leadAssistDashboard-buttonColor;
}
.chartCallDot {
@extend .dot;
background-color: $contrast-leadAssistDashboard-callRGBColor;
}
.chartEmailDot {
@extend .dot;
background-color: $contrast-leadAssistDashboard-emailRGBColor;
}
.chartTextDot {
@extend .dot;
background-color: $contrast-leadAssistDashboard-textRGBColor;
}
.eventBorder {
border-left: 4px solid $contrast-leadAssistDashboard-eventColor;
}
.teamsEventBorder{
@extend .eventBorder;
border-left: 4px solid $contrast-leadAssistDashboard-eventTeamsColor;
}
}
}

View File

@ -0,0 +1,573 @@
import * as React from 'react';
import styles from './LeadAssistDashboard.module.scss';
import { ILeadAssistDashboardProps } from './ILeadAssistDashboardProps';
import { ILeadAssistDashboardState } from './ILeadAssistDashboardState';
import * as strings from 'LeadAssistDashboardWebPartStrings';
import { WidgetSize, Dashboard, IWidget } from '@pnp/spfx-controls-react/lib/Dashboard';
import { ChartControl, ChartType } from '@pnp/spfx-controls-react/lib/ChartControl';
import { ListView, IViewField, SelectionMode } from "@pnp/spfx-controls-react/lib/ListView";
import { IColumn, Icon, Label, Separator, Spinner, SpinnerSize, TextField, initializeIcons, PrimaryButton } from 'office-ui-fabric-react';
import { Agenda, Todo } from '@microsoft/mgt-react/dist/es6/spfx';
import CustomAgendaTemplate from "./CustomAgendaTemplate";
import CustomTodoTemplate from "./CustomTodoTemplate";
import DataService from '../../../services/DataService';
import SettingsService from '../../../services/SettingsService';
import { IValueListItem } from '../../../services/IValueListItem';
import { ISaleListItem } from '../../../services/ISaleListItem';
export default class LeadAssistDashboard extends React.Component<ILeadAssistDashboardProps, ILeadAssistDashboardState> {
constructor(props) {
super(props);
// Initialize Office UI Fabric icons
initializeIcons();
// Initialize the state
this.state = {
activityCallItems: undefined,
activityEmailItems: undefined,
activityTextItems: undefined,
progressItems: undefined,
recentlyDoneSalesContractItems: undefined,
isLoading: true,
listsAreEmpty: true,
siteUrl: props.siteUrl
};
}
public async componentDidMount(): Promise<void> {
// If a site url has been specified
if (this.props.siteUrl && this.props.siteUrl.length > 0) {
// If all of the state properties containing SharePoint list items are not defined or are empty load them
if ((!this.state.activityCallItems || this.state.activityCallItems.length == 0)
&& (!this.state.activityEmailItems || this.state.activityEmailItems.length == 0)
&& (!this.state.activityTextItems || this.state.activityTextItems.length == 0)
&& (!this.state.progressItems || this.state.progressItems.length == 0)
&& (!this.state.recentlyDoneSalesContractItems || this.state.recentlyDoneSalesContractItems.length == 0)) {
try
{
// Get the list items
const callValues: IValueListItem[] = await DataService.getItemsWithValueFromList(DataService.ActivityCallsListName, (i): IValueListItem => ({ value: i[DataService.FieldValueName] }));
const emailValues: IValueListItem[] = await DataService.getItemsWithValueFromList(DataService.ActivityEmailsListName, (i): IValueListItem => ({ value: i[DataService.FieldValueName] }));
const textValues: IValueListItem[] = await DataService.getItemsWithValueFromList(DataService.ActivityTextsListName, (i): IValueListItem => ({ value: i[DataService.FieldValueName] }));
const progressValues: IValueListItem[] = await DataService.getItemsWithValueFromList(DataService.ProgressListName, (i): IValueListItem => ({ value: i[DataService.FieldValueName] }));
const recentlyDoneSalesContractValues: ISaleListItem[] = await DataService.getSalesItemsFromList(DataService.RecentlyDoneSalesContractsListName, (i): ISaleListItem => ({ id: i.Id, title: i[DataService.FieldTitleName], description: i[DataService.FieldDescriptionName]}));
// Update state
this.setState({
activityCallItems: callValues,
activityEmailItems: emailValues,
activityTextItems: textValues,
progressItems: progressValues,
recentlyDoneSalesContractItems: recentlyDoneSalesContractValues,
isLoading: false,
listsAreEmpty: callValues.length == 0
&& emailValues.length == 0
&& textValues.length == 0
&& progressValues.length == 0
&& recentlyDoneSalesContractValues.length == 0
});
}
catch (e) {
console.log(e);
this.setState({
isLoading: false
});
}
}
else {
this.setState({
isLoading: false
});
}
}
else {
this.setState({
isLoading: false,
listsAreEmpty: true
});
}
}
/**
* Handle the change of the site url
* @param event
*/
private changeSiteUrlHandler = async (event) => {
const value = event.target.value;
this.setState({
siteUrl: value
});
}
public render(): React.ReactElement<ILeadAssistDashboardProps> {
let content: JSX.Element = null;
// If the site url has been specified
if (this.props.siteUrl && this.props.siteUrl.length > 0) {
// If the state properties containing the SharePoint list items data are specified
if (this.state.activityCallItems && this.state.activityEmailItems && this.state.activityTextItems && this.state.progressItems && this.state.recentlyDoneSalesContractItems) {
// If the SharePoint lists are empty
if (this.state.listsAreEmpty == true) {
// Specify that no item has been found
content = <div className={styles.paddedContainer}>
<b>{strings.NoListItemsFound}</b>
</div>;
}
else {
// Create the dashboard element
content = <Dashboard widgets={this.getDashboardWidgets()} />;
}
}
else {
// Specify that the SharePoint lists are missing
content = <div className={styles.paddedContainer}>
<b>{strings.CreateListsLabel}</b>
</div>;
}
}
else {
// If the site url has not been yet specified returns a form to specify it
content = <div className={styles.paddedContainer}>
<div className={styles.padding5}>
<Label>{strings.TargetSiteUrl}</Label>
<TextField value={this.props.siteUrl} onChange={this.changeSiteUrlHandler} />
</div>
<div className={styles.padding5}>
<PrimaryButton text={strings.SaveConfiguration} onClick={() => { SettingsService.saveSiteUrl(this.props.graphClient, { siteUrl: this.state.siteUrl }); }} />
</div>
</div>;
}
return (
<div className={styles.leadAssistDashboard}>
{this.state.isLoading && <Spinner size={SpinnerSize.large} title={strings.Loading} className={styles.loader} />}
{!this.state.isLoading && content}
</div>
);
}
/**
* Get the dashboard widgets
* @returns An array of the widgets to be added to the dashboard element
*/
private getDashboardWidgets() : IWidget[] {
return [{
title: strings.ActivityChartTitle,
size: WidgetSize.Double,
body: [
{
id: "activityChartTab",
title: strings.ActivityChartTitle,
content: (
this.getActivityChartTab()
)
}
]
},
{
title: strings.ProgressChartTitle,
size: this.props.isTeamsContext ? WidgetSize.Double : WidgetSize.Single,
body: [
{
id: "progressChartTab",
title: strings.ProgressChartTitle,
content: (
this.getProgressChartTab()
)
}
]
},
{
title: strings.MyDayTitle,
size: WidgetSize.Single,
body: [
{
id: "myDayTab",
title: strings.MyDayTitle,
content: (
this.getMyDayTab()
)
}
]
},
{
title: strings.RecentlyDoneSalesTitle,
size: this.props.isTeamsContext ? WidgetSize.Double : WidgetSize.Single,
body: [
{
id: "recentlyDoneSalesTab",
title: strings.RecentlyDoneSalesTitle,
content: (
this.getRecentlyDoneSalesTab()
)
}
]
},
{
title: strings.ToDoTitle,
size: WidgetSize.Single,
body: [
{
id: "toDoTab",
title: strings.ToDoTitle,
content: (
this.getToDoTab()
)
}
]
}];
}
/**
* Get the content for the Activity chart widget
* @returns Element representing the Activity chart tab
*/
private getActivityChartTab() {
const data = this.getActivityData();
// Options for the chart element
const options = {
legend: {
display: false,
},
title: {
display: false
},
responsive: true,
maintainAspectRatio: false
};
const accessibility = {
enable: true
};
// Get the total for each category
const callsTotal = data.datasets[0].data.reduce((sum, current) => sum + current, 0);
const emailsTotal = data.datasets[1].data.reduce((sum, current) => sum + current, 0);
const textsTotal = data.datasets[2].data.reduce((sum, current) => sum + current, 0);
return <div>
<div className={styles.grid} dir="ltr">
<div className={`${styles.row} ${styles.padding5}`}>
<div className={(this.props.isTeamsContext) ? styles.smallColumnTeams : styles.smallColumn}>
<div>
<span className={styles.chartCallDot}></span>
{strings.ActivityChartLegendCalls}
</div>
<div className={styles.chartNumber}>
<b>{callsTotal}</b>
</div>
</div>
<div className={styles.smallColumn}>
<div>
<span className={styles.chartEmailDot}></span>
{strings.ActivityChartLegendEmails}
</div>
<div className={styles.chartNumber}>
<b>{emailsTotal}</b>
</div>
</div>
<div className={styles.smallColumn}>
<div>
<span className={styles.chartTextDot}></span>
{strings.ActivityChartLegendTexts}
</div>
<div className={styles.chartNumber}>
<b>{textsTotal}</b>
</div>
</div>
</div>
</div>
<div>
<ChartControl
type={ChartType.Line}
data={data}
accessibility={accessibility}
options={options}
loadingtemplate={() => <Spinner size={SpinnerSize.large} label={strings.Loading} />}
/>
</div>
</div>;
}
/**
* Get the content for the Progress chart widget
* @returns Element representing the Progress chart tab
*/
private getProgressChartTab() {
// Get data
const data1 = this.getProgressData(0);
const data2 = this.getProgressData(1);
const data3 = this.getProgressData(2);
// Options for the chart element
const options = {
legend: {
display: false,
},
title: {
display: false
},
responsive: true,
maintainAspectRatio: false,
circumference: Math.PI,
rotation: Math.PI
};
const accessibility = {
enable: true
};
return <div className={styles.centeredContainer}>
<div className={styles.grid} dir="ltr">
<div className={`${styles.row} ${styles.marginLeft}`}>
<div className={(this.props.isTeamsContext == true) ? styles.chartColumnTeams : styles.chartColumn}>
<ChartControl
type={ChartType.Doughnut}
data={data1}
accessibility={accessibility}
options={options}
loadingtemplate={() => <Spinner size={SpinnerSize.large} label={strings.Loading} />}
/>
<div className={styles.progressDescription}>
<b>{strings.ProgressSales}</b>
</div>
</div>
<div className={(this.props.isTeamsContext == true) ? styles.chartColumnTeams : styles.chartColumn}>
<ChartControl
type={ChartType.Doughnut}
data={data2}
accessibility={accessibility}
options={options}
loadingtemplate={() => <Spinner size={SpinnerSize.large} label={strings.Loading} />}
/>
<div className={styles.progressDescription}>
<b>{strings.ProgressRevenues}</b>
</div>
</div>
<div className={(this.props.isTeamsContext == true) ? styles.chartColumnTeams : styles.chartColumn}>
<ChartControl
type={ChartType.Doughnut}
data={data3}
accessibility={accessibility}
options={options}
loadingtemplate={() => <Spinner size={SpinnerSize.large} label={strings.Loading} />}
/>
<div className={styles.progressDescription}>
<b>{strings.ProgressMarketShare}</b>
</div>
</div>
</div>
</div>
<Separator />
<div className={styles.tip}>
{strings.ProgressChartTip}
</div>
</div>;
}
/**
* Get the content for the My Day widget
* @returns Element representing the My Day tab
*/
private getMyDayTab() {
return <Agenda
days={30}>
<CustomAgendaTemplate.loadingTemplate template="loading" />
<CustomAgendaTemplate.eventTemplate template="event" />
<CustomAgendaTemplate.noDataTemplate template="no-data" />
</Agenda>;
}
/**
* Get the content for the Recently done sales widget
* @returns Element representing the Recently done sales tab
*/
private getRecentlyDoneSalesTab() {
// Get items to display
const items = this.state.recentlyDoneSalesContractItems;
// Define the columns of the ListView element
const viewFields: IViewField[] = [
{
name: "title",
displayName: strings.RecentlyDoneSalesViewTitle,
sorting: true,
isResizable: true,
minWidth: 100
},
{
name: "description",
displayName: strings.RecentlyDoneSalesViewDescription,
sorting: true,
isResizable: true,
minWidth: 100
},
{
name: "buttonColumn",
displayName: " ",
minWidth: 50,
render: (item?: any, index?: number, column?: IColumn) => {
var content = <div></div>;
// If the element exists
if (item) {
// Create an clickable icon to open the SharePoint list item
content = <Icon iconName={'Link'}
onMouseOver={() => {}}
onClick={() => {
const addSlash = this.props.siteUrl.endsWith("/") == false;
var tempLink = document.createElement('a');
tempLink.href = this.props.siteUrl + ((addSlash == true) ? "/" : "") + "Lists/" + DataService.RecentlyDoneSalesContractsListName + "/DispForm.aspx?ID=" + item.id;
tempLink.target = "_blank";
tempLink.click();
}} />;
}
return content;
}
}
];
return <div>
<ListView
items={items}
viewFields={viewFields}
iconFieldName="ServerRelativeUrl"
compact={true}
selectionMode={SelectionMode.none}
showFilter={false}
filterPlaceHolder={strings.RecentlyDoneSalesViewFilterPlaceHolder}
sortItems={this.sortItems}
stickyHeader={true} />
</div>;
}
/**
* Get the content for the ToDo widget
* @returns Element representing the ToDo tab
*/
private getToDoTab() {
return <Todo hideHeader={true}>
<CustomTodoTemplate.todoTemplate template="task" />
</Todo>;
}
/**
* Get the data to be displayed in the Activity widget
* @returns Datasets for the chart control in the Activity widget
*/
private getActivityData() {
return {
labels: DataService.MonthNames,
datasets: [
{
label: strings.ActivityChartLegendCalls,
fill: false,
data: this.state.activityCallItems.map(i => i.value),
backgroundColor: styles.callRGBColor,
borderColor: styles.callRGBColor,
borderWidth: 3
},
{
label: strings.ActivityChartLegendEmails,
fill: false,
data: this.state.activityEmailItems.map(i => i.value),
backgroundColor: styles.emailRGBColor,
borderColor: styles.emailRGBColor,
borderWidth: 3
},
{
label: strings.ActivityChartLegendTexts,
fill: false,
data: this.state.activityTextItems.map(i => i.value),
backgroundColor: styles.textRGBColor,
borderColor: styles.textRGBColor,
borderWidth: 3
},
]
};
}
/**
* Get data for the Progress widget
* @param index Index to differentiate the chunks, available values are 0,1,2
* @returns
*/
private getProgressData(index: number) {
const totalLength = this.state.progressItems.length;
// Get the chunk length
const chunk = totalLength / 3;
// Get the items of the chunk
const items = this.state.progressItems.slice(chunk * index, chunk * (index + 1));
return {
labels: DataService.MonthNames,
datasets: [
{
label: strings.ProgressChartTitle,
data: items.map(i => i.value)
}
]
};
}
/**
* Sort the specified items for a specific column
* @param items Items to be sorted
* @param columnName Column used for sorting
* @param descending Specify if the sorting is descending
* @returns Input items sorted for the specified column
*/
private sortItems = (items: any[], columnName: string, descending: boolean): any[] => {
let properties: string[];
// Support for nested properties
if (columnName.toString().indexOf(".") > 0) {
properties = columnName.toString().split(".");
}
if (!items || items.length == 0) {
return items;
}
return items.sort((a, b) => {
if (a === null || a === undefined) {
return 1;
}
else if (b === null || b === undefined) {
return -1;
}
else if (a === b) {
return 0;
}
var valueA = a[columnName];
var valueB = b[columnName];
// If it's a complex property
if (properties && properties.length > 0) {
valueA = a[properties[0]][properties[1]];
valueB = b[properties[0]][properties[1]];
}
var dateValueB = new Date(valueB.toString());
var dateValueA = new Date(valueA.toString());
// Check if the value is a date
if ((Object.prototype.toString.call(dateValueA) === "[object Date]" && !isNaN(dateValueA.getTime()))
&& (Object.prototype.toString.call(dateValueB) === "[object Date]" && !isNaN(dateValueB.getTime()))) {
if (descending) {
return (dateValueB.getTime() - dateValueA.getTime()) > 0 ? 1 : -1;
}
else {
return (dateValueA.getTime() - dateValueB.getTime()) > 0 ? 1 : -1;
}
}
return (descending ? valueA < valueB : valueA > valueB) ? -1 : 1;
});
}
}

View File

@ -0,0 +1,79 @@
define([], function() {
return {
"PropertyPaneDescription": "Use the buttons to populate or delete demo data.",
"GenerateDemoDataGroupName": "Generate demo data",
"CleanDemoDataGroupName": "Remove demo data",
"GenerateSharePointDemoListsButton": "Create SharePoint demo lists",
"GenerateSharePointDemoDataButton": "SharePoint demo data",
"GenerateGraphDemoDataButton": "Graph demo data",
"DeleteDemoDataButton": "Clean SharePoint demo lists",
"ConfirmCreateDemoLists": "Confirm to create demo lists?",
"ConfirmAddDemoData": "Confirm to add demo data to the demo lists?",
"ConfirmAddGraphDemoData": "Confirm to add demo data via Microsoft Graph?",
"ConfirmDeleteSharePointDemoLists": "Confirm to delete all the SharePoint demo lists and their contents?",
"DemoListsCreated": "Demo lists created.",
"DemoDataGenerated": "Demo data added to the demo lists.",
"GraphDemoDataGenerated": "Demo data added via Microsoft Graph.",
"SharePointDemoListsDeleted": "SharePoint demo lists deleted.",
"ActivityChartTitle": "Activity overview",
"ActivityChartLegendCalls": "Team calls",
"ActivityChartLegendEmails": "Emails",
"ActivityChartLegendTexts": "Texts",
"ProgressChartTitle": "Progress",
"ProgressChartTip": "Tip: You would have to talk to 15 leads per day to achieve your sales goals per your current lead conversion rate.",
"ProgressSales": "Sales",
"ProgressRevenues": "Revenues",
"ProgressMarketShare": "Market Share",
"MyDayTitle": "My day",
"RecentlyDoneSalesTitle": "Open leads",
"RecentlyDoneSalesSeeAll": "See all",
"RecentlyDoneSalesViewTitle": "Title",
"RecentlyDoneSalesViewDescription": "Description",
"RecentlyDoneSalesViewFilterPlaceHolder": "Search",
"ToDoTitle": "To-do",
"ToDoSeeAll": "See all",
"MicrosoftTeams": "Microsoft Teams",
"Loading": "Loading...",
"NoEventFound": "No event found",
"NoListItemsFound": "No list items found, populate the lists via the web part property pane and then refresh the page.",
"CreateListsLabel": "No lists found, create the demo lists via the web part property pane to use the Dashboard.",
"DemoDocumentTitle": "Test",
"DemoDocumentDescription": "Test description",
"DemoActivityCallTitle": "Call",
"DemoActivityEmailTitle": "Email",
"DemoActivityTextTitle": "Text",
"DemoTaskTitle": "Demo task",
"DemoTaskWithDateTitle": "Demo task with date",
"DemoApplicationName": "Lead Assist Dashboard",
"DemoDisplayName": "PnP",
"DemoEventTitle": "Demo event",
"DemoEventContent": "This is a demo event",
"DemoEventLocation": "Office",
"DemoEventOnlineTitle": "Online demo event",
"DemoEventOnlineContent": "This is an online demo event",
"ActivityCallsListName": "Activity Teams Calls Demo Data",
"ActivityEmailsListName": "Activity Emails Demo Data",
"ActivityTextsListName": "Activity Texts Demo Data",
"ProgressListName": "Progress Demo Data",
"RecentlyDoneSalesContractsListName": "Recently Done Sales Contracts Demo Data",
"TargetSiteUrl": "Specify target site URL",
"SaveConfiguration": "Save",
"January": "January",
"February": "February",
"March": "March",
"April": "April",
"May": "May",
"June": "June",
"July": "July"
}
});

View File

@ -0,0 +1,82 @@
declare interface ILeadAssistDashboardWebPartStrings {
PropertyPaneDescription: string;
GenerateDemoDataGroupName: string;
CleanDemoDataGroupName: string;
GenerateSharePointDemoListsButton: string;
GenerateSharePointDemoDataButton: string;
GenerateGraphDemoDataButton: string;
DeleteDemoDataButton: string;
ConfirmCreateDemoLists: string;
ConfirmAddDemoData: string;
ConfirmAddGraphDemoData: string;
ConfirmDeleteSharePointDemoLists: string;
DemoListsCreated: string;
DemoDataGenerated: string;
GraphDemoDataGenerated: string;
SharePointDemoListsDeleted: string;
ActivityChartTitle: string;
ActivityChartLegendCalls: string;
ActivityChartLegendEmails: string;
ActivityChartLegendTexts: string;
ProgressChartTitle: string;
ProgressChartTip: string;
ProgressSales: string;
ProgressRevenues: string;
ProgressMarketShare: string;
MyDayTitle: string;
RecentlyDoneSalesTitle: string;
RecentlyDoneSalesSeeAll: string;
RecentlyDoneSalesViewTitle: string;
RecentlyDoneSalesViewDescription: string;
RecentlyDoneSalesViewFilterPlaceHolder: string;
ToDoTitle: string;
ToDoSeeAll: string;
MicrosoftTeams: string;
Loading: string;
NoEventFound: string;
NoListItemsFound: string;
CreateListsLabel: string;
DemoDocumentTitle: string;
DemoDocumentDescription: string;
DemoActivityCallTitle: string;
DemoActivityEmailTitle: string;
DemoActivityTextTitle: string;
DemoTaskTitle: string;
DemoTaskWithDateTitle: string;
DemoApplicationName: string;
DemoDisplayName: string;
DemoEventTitle: string;
DemoEventContent: string;
DemoEventLocation: string;
DemoEventOnlineTitle: string;
DemoEventOnlineContent: string;
ActivityCallsListName: string;
ActivityEmailsListName: string;
ActivityTextsListName: string;
ProgressListName: string;
RecentlyDoneSalesContractsListName: string;
TargetSiteUrl: string;
SaveConfiguration: string;
January: string;
February: string;
March: string;
April: string;
May: string;
June: string;
July: string;
}
declare module 'LeadAssistDashboardWebPartStrings' {
const strings: ILeadAssistDashboardWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "9899641b-7026-46c6-92a3-66f052075a82",
"alias": "LeadAssistDashboardSettingsWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": [ "TeamsPersonalApp" ],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "Lead Assist Dashboard Settings" },
"description": { "default": "Lead Assist Dashboard Settings" },
"officeFabricIconFontName": "Settings",
"properties": {
"description": "LeadAssistDashboardSettings"
}
}]
}

View File

@ -0,0 +1,91 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { sp } from "@pnp/sp/presets/all";
import * as strings from 'LeadAssistDashboardSettingsWebPartStrings';
import LeadAssistDashboardSettings from './components/LeadAssistDashboardSettings';
import { ILeadAssistDashboardSettingsProps } from './components/ILeadAssistDashboardSettingsProps';
import SettingsService from '../../services/SettingsService';
export interface ILeadAssistDashboardSettingsWebPartProps {
}
export default class LeadAssistDashboardSettingsWebPart extends BaseClientSideWebPart<ILeadAssistDashboardSettingsWebPartProps> {
private siteUrl: string;
protected async onInit() {
if (this.context.sdks.microsoftTeams != undefined) {
const teamsContext = this.context.sdks.microsoftTeams.context;
this.applyTheme(teamsContext.theme || 'default');
this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler(this.applyTheme);
}
const graphClient = await this.context.msGraphClientFactory.getClient();
// Get the settings
const settings = await SettingsService.getSettings(graphClient, this.context.httpClient);
// If there are settings specified
if (settings) {
// Get the site URL
this.siteUrl = settings.siteUrl;
}
// If site url has been specified
if (this.siteUrl && this.siteUrl.length > 0) {
// Setup the SharePoint client
sp.setup({
spfxContext: this.context,
sp: {
baseUrl: this.siteUrl
}
});
}
}
private applyTheme = (theme: string): void => {
this.context.domElement.setAttribute('data-theme', theme);
document.body.setAttribute('data-theme', theme);
}
public render(): void {
const element: React.ReactElement<ILeadAssistDashboardSettingsProps> = React.createElement(
LeadAssistDashboardSettings,
{}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,4 @@
import { MSGraphClient } from "@microsoft/sp-http";
export interface ILeadAssistDashboardSettingsProps {
}

View File

@ -0,0 +1,47 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.leadAssistDashboardSettings {
.container {
max-width: 700px;
margin: 0px auto;
}
.buttonRow {
margin: 3px;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,83 @@
import * as React from 'react';
import styles from './LeadAssistDashboardSettings.module.scss';
import { ILeadAssistDashboardSettingsProps } from './ILeadAssistDashboardSettingsProps';
import DataService from '../../../services/DataService';
import * as strings from 'LeadAssistDashboardWebPartStrings';
import * as settingsStrings from 'LeadAssistDashboardSettingsWebPartStrings';
import { PrimaryButton } from 'office-ui-fabric-react';
import { WidgetSize, Dashboard, IWidget } from '@pnp/spfx-controls-react/lib/Dashboard';
export default class LeadAssistDashboardSettings extends React.Component<ILeadAssistDashboardSettingsProps, {}> {
private async generateSharePointDemoListsClick(): Promise<void> {
await DataService.generateDemoLists();
}
private async generateSharePointDemoDataClick(): Promise<void> {
await DataService.generateListsDemoData();
}
private async generateGraphDemoDataClick(): Promise<void> {
await DataService.generateGraphDemoData();
}
private async deleteSharePointDemoListsClick(): Promise<void> {
await DataService.deleteSharePointDemoLists();
}
public render(): React.ReactElement<ILeadAssistDashboardSettingsProps> {
const content: JSX.Element = <Dashboard widgets={this.getDashboardWidgets()} />;
return (
<div className={ styles.leadAssistDashboardSettings }>
{content}
</div>
);
}
/**
* Get the dashboard widgets
* @returns An array of the widgets to be added to the dashboard element
*/
private getDashboardWidgets() : IWidget[] {
return [{
title: settingsStrings.DataSettingsTabTitle,
size: WidgetSize.Single,
body: [
{
id: "settingsTab",
title: settingsStrings.DataSettingsTabTitle,
content: (
<div className={ styles.container }>
<div>
{strings.PropertyPaneDescription}
</div>
<br />
<div>
{strings.GenerateDemoDataGroupName}
</div>
<div>
<div className={styles.buttonRow}>
<PrimaryButton text={strings.GenerateSharePointDemoListsButton} onClick={this.generateSharePointDemoListsClick.bind(this)} />
</div>
<div className={styles.buttonRow}>
<PrimaryButton text={strings.GenerateSharePointDemoDataButton} onClick={this.generateSharePointDemoDataClick.bind(this)} />
</div>
<div className={styles.buttonRow}>
<PrimaryButton text={strings.GenerateGraphDemoDataButton} onClick={this.generateGraphDemoDataClick.bind(this)} />
</div>
</div>
<br/>
<div>
{strings.CleanDemoDataGroupName}
</div>
<div>
<div className={styles.buttonRow}>
<PrimaryButton text={strings.DeleteDemoDataButton} onClick={this.deleteSharePointDemoListsClick.bind(this)} />
</div>
</div>
</div>
)
}
]
}];
}
}

View File

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

View File

@ -0,0 +1,10 @@
declare interface ILeadAssistDashboardSettingsWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DataSettingsTabTitle: string;
}
declare module 'LeadAssistDashboardSettingsWebPartStrings' {
const strings: ILeadAssistDashboardSettingsWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,63 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.9/MicrosoftTeams.schema.json",
"manifestVersion": "1.9",
"version": "1.0.0",
"showLoadingIndicator": false,
"id": "1c198bf5-aae4-497a-ac0f-74b78a77b0aa",
"packageName": "com.pnp.leadassistdashboard",
"developer": {
"name": "PnP",
"websiteUrl": "https://pnp.github.io/",
"privacyUrl": "https://pnp.github.io/",
"termsOfUseUrl": "https://pnp.github.io/"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "PnP Lead Assist Dashboard",
"full": "PnP Lead Assist Dashboard"
},
"description": {
"short": "Example short description",
"full": "Exampe full description"
},
"accentColor": "#FFFFFF",
"staticTabs": [
{
"entityId": "70001",
"name": "Dashboard",
"contentUrl": "https://{teamSiteDomain}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest=/_layouts/15/teamshostedapp.aspx%3Fteams%26personal%26componentId=29c7e411-c4ec-4592-8b24-dbfd8bd1e1ca%26forceLocale={locale}",
"scopes": [
"personal"
]
},
{
"entityId": "70002",
"name": "Settings",
"contentUrl": "https://{teamSiteDomain}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest=/_layouts/15/teamshostedapp.aspx%3Fteams%26personal%26componentId=9899641b-7026-46c6-92a3-66f052075a82%26forceLocale={locale}",
"scopes": [
"personal"
]
},
{
"entityId": "about",
"scopes": [
"personal"
]
}
],
"permissions": [
"identity"
],
"validDomains": [
"piasysdev.sharepoint.com",
"*.login.microsoftonline.com",
"*.sharepoint-df.com",
"spoppe-a.akamaihd.net",
"spoprod-a.akamaihd.net",
"resourceseng.blob.core.windows.net",
"msft.spoppe.com"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View File

@ -0,0 +1,35 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.7/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection",
"es2015.promise"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "./node_modules/@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}