Merge branch 'dev'
This commit is contained in:
commit
50ca5fdb85
|
@ -0,0 +1,25 @@
|
||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
|
||||||
|
[*]
|
||||||
|
|
||||||
|
# change these settings to your own preference
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# we recommend you to keep these unchanged
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[{package,bower}.json]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Build generated files
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
solution
|
||||||
|
temp
|
||||||
|
*.sppkg
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Visual Studio files
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
.vs
|
||||||
|
bin
|
||||||
|
obj
|
||||||
|
|
||||||
|
# Resx Generated Code
|
||||||
|
*.resx.ts
|
||||||
|
|
||||||
|
# Styles Generated Code
|
||||||
|
*.scss.ts
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"libraryName": "happy-birdthay",
|
||||||
|
"libraryId": "57890dd1-b655-4ec8-85ec-e47a9b696e7c",
|
||||||
|
"environment": "spo",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
# React Birthdays Web Part
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
The Web Part Birthdays shows the upcoming birthdays in the company, the web part reads birthdays from a list located on the tenant's home site with title "Birthdays."
|
||||||
|
|
||||||
|
|
||||||
|
There is an Azure function available that get AAD user birthdays, this function creates a list on the tenant root site, if it does not exist.
|
||||||
|
See the local.settings.json for details on the required application variable located in SyncUsersBirthdaysFunction folder.
|
||||||
|
|
||||||
|
But you can synchronize the Birthdays list with other applications HR Systems, or other sources
|
||||||
|
|
||||||
|
|
||||||
|
![Brithdays Web Part](./assets/birthdays.png)
|
||||||
|
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||||
|
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||||
|
|
||||||
|
> Update accordingly as needed.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Existing list in tenant root site, with the Title "Birthdays" and columns:
|
||||||
|
|
||||||
|
Column Internal Name|Type|Required| comments
|
||||||
|
--------------------|----|--------|----------
|
||||||
|
jobTitle| Text| no|
|
||||||
|
Birthday| DateTime | true|
|
||||||
|
userAADGUID| Text| no | required if used Azure Function to get Birthdays from AAD
|
||||||
|
Title| Text| true
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react Birthday Web Part|João Mendes
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0.0|November 6, 2018|Initial release
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
|
- Clone this repository
|
||||||
|
- in the command line run:
|
||||||
|
- `npm install`
|
||||||
|
- `gulp build`
|
||||||
|
- `gulp bundle --ship`
|
||||||
|
- `gulp package-solution --ship`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
This project contains sample Birthday web parts built on the SharePoint Framework using React
|
||||||
|
and an Azure Function to get user Birthdays from AAD.
|
||||||
|
This sample illustrates the following concepts on top of the SharePoint Framework:
|
||||||
|
- using React for building SharePoint Framework client-side web parts
|
||||||
|
- using React components for building Birthday web part
|
||||||
|
- using MSGraph API to get data from SharePoint Lists
|
||||||
|
- using MSGraph API to read users from AAD
|
||||||
|
- using @PnP/PnPjs to create a List, add, update, delete Items.
|
||||||
|
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />
|
Binary file not shown.
After Width: | Height: | Size: 137 KiB |
Binary file not shown.
After Width: | Height: | Size: 270 KiB |
Binary file not shown.
After Width: | Height: | Size: 218 KiB |
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"happy-birdthay-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/Birthdays/BirthdaysWebPart.js",
|
||||||
|
"manifest": "./src/webparts/Birthdays/BirthdaysWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"ControlStrings": "lib/controls/HappyBirthdayCard/loc/{locale}.js",
|
||||||
|
"BirthdaysWebPartStrings": "lib/webparts/Birthdays/loc/{locale}.js",
|
||||||
|
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"includeExtensions": [
|
||||||
|
"png",
|
||||||
|
"jpg",
|
||||||
|
"svg"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "happy-birdthay",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "happy-birdthay-client-side-solution",
|
||||||
|
"id": "57890dd1-b655-4ec8-85ec-e47a9b696e7c",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"skipFeatureDeployment": true,
|
||||||
|
"isDomainIsolated": false
|
||||||
|
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/birdthays.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const build = require('@microsoft/sp-build-web');
|
||||||
|
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"name": "Birthdays",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/loader-set-webpack-public-path": "^3.2.102",
|
||||||
|
"@microsoft/sp-core-library": "1.7.0",
|
||||||
|
"@microsoft/sp-lodash-subset": "1.7.0",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
|
||||||
|
"@microsoft/sp-webpart-base": "1.7.0",
|
||||||
|
"@pnp/common": "^1.2.5",
|
||||||
|
"@pnp/graph": "^1.2.5",
|
||||||
|
"@pnp/logging": "^1.2.5",
|
||||||
|
"@pnp/odata": "^1.2.5",
|
||||||
|
"@pnp/sp": "^1.2.5",
|
||||||
|
"@pnp/spfx-controls-react": "1.10.0",
|
||||||
|
"@pnp/spfx-property-controls": "1.12.0",
|
||||||
|
"@types/es6-promise": "0.0.33",
|
||||||
|
"@types/react": "16.4.2",
|
||||||
|
"@types/react-dom": "16.0.5",
|
||||||
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"moment": "^2.22.2",
|
||||||
|
"react": "16.3.2",
|
||||||
|
"react-dom": "16.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/set-webpack-public-path-plugin": "^2.1.58",
|
||||||
|
"@microsoft/sp-build-web": "1.7.0",
|
||||||
|
"@microsoft/sp-module-interfaces": "1.7.0",
|
||||||
|
"@microsoft/sp-tslint-rules": "1.7.0",
|
||||||
|
"@microsoft/sp-webpart-workbench": "1.7.0",
|
||||||
|
"@types/chai": "3.4.34",
|
||||||
|
"@types/mocha": "2.2.38",
|
||||||
|
"ajv": "~5.2.2",
|
||||||
|
"gulp": "~3.9.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"name": "myTimer",
|
||||||
|
"type": "timerTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"schedule": "0 */5 * * * *"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"version": "2.0"
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
const request = require('request-promise');
|
||||||
|
const sp = require('@pnp/sp').sp;
|
||||||
|
const Web = require('@pnp/sp').Web;
|
||||||
|
const SPFetchClient = require("@pnp/nodejs").SPFetchClient;
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
const tenantId = GetEnvironmentVariable('TenantId');
|
||||||
|
const tenantUrl = GetEnvironmentVariable('TenantUrl');
|
||||||
|
const clientId = GetEnvironmentVariable('GraphClientId');
|
||||||
|
const clientSecret = GetEnvironmentVariable('GraphClientSecret');
|
||||||
|
const birthdayListTitle = GetEnvironmentVariable('BirthdayListTitle');
|
||||||
|
|
||||||
|
let _context;
|
||||||
|
let _msgraphToken;
|
||||||
|
let _deltaLink;
|
||||||
|
let _appCatalog;
|
||||||
|
|
||||||
|
module.exports = async function (context, myTimer) {
|
||||||
|
// Main
|
||||||
|
var timeStamp = new Date().toISOString();
|
||||||
|
if (myTimer.isPastDue) {
|
||||||
|
context.log('User Birthdays Sync is running late!');
|
||||||
|
}
|
||||||
|
context.log(`User Birthdays Sync started at ${timeStamp}`);
|
||||||
|
try {
|
||||||
|
// onInit function
|
||||||
|
await onInit(context, myTimer);
|
||||||
|
const result = await ensureBirthdaysList();
|
||||||
|
// Read Users
|
||||||
|
if (result) {
|
||||||
|
await getAllUsers(_deltaLink);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.log(`Error Create or Access Birthday List ${timeStamp}`);
|
||||||
|
// process.exit(1);
|
||||||
|
}
|
||||||
|
// End
|
||||||
|
context.log(`User Birthdays Sync ended at ${timeStamp}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
context.log(error);
|
||||||
|
// process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Get Enviroment Variavel
|
||||||
|
function GetEnvironmentVariable(name) {
|
||||||
|
return process.env[name];
|
||||||
|
}
|
||||||
|
// Get MSGraph Token
|
||||||
|
async function GetMSGraphToken() {
|
||||||
|
let access_token = null;
|
||||||
|
|
||||||
|
const payload = `client_id=${clientId}&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=${clientSecret}&grant_type=client_credentials`;
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
uri: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
|
||||||
|
body: payload,
|
||||||
|
json: true
|
||||||
|
};
|
||||||
|
// Request
|
||||||
|
try {
|
||||||
|
const result = await request(options)
|
||||||
|
if (result && result.access_token) {
|
||||||
|
access_token = result.access_token;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_context.log(`Error getting AccessToken, error: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return access_token;
|
||||||
|
|
||||||
|
}
|
||||||
|
// Get users
|
||||||
|
async function GetUsers(uri) {
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
"Authorization": `Bearer ${_msgraphToken}`
|
||||||
|
},
|
||||||
|
uri: uri,
|
||||||
|
json: true
|
||||||
|
};
|
||||||
|
return users = request(options)
|
||||||
|
|
||||||
|
}
|
||||||
|
// Get Birthday of User
|
||||||
|
async function GetUsersBirthday(userId) {
|
||||||
|
let results = null;
|
||||||
|
const options = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
"Authorization": `Bearer ${_msgraphToken}`
|
||||||
|
},
|
||||||
|
uri: `https://graph.microsoft.com/v1.0/users/${userId}/birthday`,
|
||||||
|
json: true
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
results = await request(options);
|
||||||
|
} catch (e) {
|
||||||
|
_context.log(`error getting birthday date for user ${userId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return value
|
||||||
|
return results.value ? results.value : null;
|
||||||
|
|
||||||
|
}
|
||||||
|
// Read all Users
|
||||||
|
async function getAllUsers(uri) {
|
||||||
|
try {
|
||||||
|
// get users 'https://graph.microsoft.com/v1.0/users/delta?$select=displayName,jobTitle,mail,Id'; or nextLink URL
|
||||||
|
const _users = await GetUsers(uri);
|
||||||
|
// has data?
|
||||||
|
if ( _users && _users.value && _users.value.length === 0 ){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// get deltaLink for track changes.
|
||||||
|
// get nextLink to get next page
|
||||||
|
const _nextLink = (typeof _users["@odata.nextLink"] !== undefined) ? _users["@odata.nextLink"] : undefined;
|
||||||
|
const _deltaLink = (typeof _users["@odata.deltaLink"] !== undefined) ? _users["@odata.deltaLink"] : undefined;
|
||||||
|
// Read Users
|
||||||
|
for (const user of _users.value) {
|
||||||
|
_context.log(user.displayName);
|
||||||
|
// If user was removed from AAD
|
||||||
|
try {
|
||||||
|
if (user['@removed']) {
|
||||||
|
await deleteUser(user);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_birthday = await GetUsersBirthday(user.id);
|
||||||
|
const _year = moment(_birthday.toString()).format('YYYY');
|
||||||
|
// The Birthday Date has year 2000
|
||||||
|
if (_year === '2000') {
|
||||||
|
// check if user exists
|
||||||
|
_exists = await checkUserExist(user);
|
||||||
|
if (!_exists) {
|
||||||
|
// Add user to List
|
||||||
|
await addUser(user, _birthday)
|
||||||
|
} else {
|
||||||
|
//Update user
|
||||||
|
await updateUser(user, _birthday)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
_context.log(`Error Adding or Updating users : ${error} `);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Load next Page
|
||||||
|
if (_nextLink) {
|
||||||
|
await getAllUsers(_nextLink);
|
||||||
|
}
|
||||||
|
// deltaLink exist (last request)
|
||||||
|
if (_deltaLink) {
|
||||||
|
// Save Tenant property with deltaLink for track changes
|
||||||
|
await _appCatalog.setStorageEntity("UserBirthdayDeltaLink", _deltaLink, "Users Sync Delta Token");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
_context.log(`Error updating StorageEntity : ${error} `);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
_context.log(`Error on read users : ${error} `);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
// onInit Function
|
||||||
|
async function onInit(context, myTimer) {
|
||||||
|
_context = context;
|
||||||
|
try {
|
||||||
|
// setup PnPJs
|
||||||
|
sp.setup({
|
||||||
|
sp: {
|
||||||
|
fetchClientFactory: () => {
|
||||||
|
return new SPFetchClient(
|
||||||
|
tenantUrl,
|
||||||
|
clientId,
|
||||||
|
clientSecret);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Get MSGraph Token
|
||||||
|
_msgraphToken = await GetMSGraphToken();
|
||||||
|
// Get last deltaLink from Tenant Property
|
||||||
|
_appCatalog = new Web(`${tenantUrl}/sites/appcatalog`);
|
||||||
|
const _deltaLinkValue = await _appCatalog.getStorageEntity('UserBirthdayDeltaLink');
|
||||||
|
_deltaLink = _deltaLinkValue.Value ? _deltaLinkValue.Value : 'https://graph.microsoft.com/v1.0/users/delta?$select=displayName,jobTitle,mail,Id';
|
||||||
|
_context.log(_deltaLink);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
context.log(`Error on onInit function: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Add User to SP List
|
||||||
|
async function addUser(user, _birthday) {
|
||||||
|
let _item = null;
|
||||||
|
try {
|
||||||
|
_item = await sp.web.lists.getByTitle(birthdayListTitle).items.add({
|
||||||
|
Title: user.displayName,
|
||||||
|
jobTitle: user.jobTitle ? user.jobTitle : '',
|
||||||
|
email: user.mail ? user.mail : '',
|
||||||
|
userAADGUID: user.id,
|
||||||
|
Birthday: moment(_birthday.toString()).format('YYYY-MM-DD')
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
__context.log(`Èrror adding user ${user.displayName}, error: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _item ? _item : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update User Data
|
||||||
|
async function updateUser(user, _birthday) {
|
||||||
|
let _userUpdated = null;
|
||||||
|
const _item = await sp.web.lists.getByTitle(birthdayListTitle).items.top(1).filter(`userAADGUID eq '${user.id}'`).get()
|
||||||
|
|
||||||
|
if (_item && _item.length > 0) {
|
||||||
|
_userUpdated = await sp.web.lists.getByTitle(birthdayListTitle).items.getById(_item[0].Id).update({
|
||||||
|
Title: user.displayName,
|
||||||
|
jobTitle: user.jobTitle ? user.jobTitle : '',
|
||||||
|
email: user.mail ? user.mail : '',
|
||||||
|
userAADGUID: user.id,
|
||||||
|
Birthday: moment(_birthday.toString()).format('YYYY-MM-DD')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// return value
|
||||||
|
return _userUpdated ? _userUpdated : null;
|
||||||
|
}
|
||||||
|
// check if user Exist in SPList
|
||||||
|
async function checkUserExist(user) {
|
||||||
|
let _item = null;
|
||||||
|
try {
|
||||||
|
_item = await sp.web.lists.getByTitle(birthdayListTitle).items.top(1).filter(`userAADGUID eq '${user.id}'`).get()
|
||||||
|
} catch (e) {
|
||||||
|
__context.log(`Error Checking if user ${user.displayName} exists, error: ${e} `);
|
||||||
|
}
|
||||||
|
// Return Value
|
||||||
|
return _item && _item.length > 0 ? true : false;
|
||||||
|
|
||||||
|
}
|
||||||
|
// Delete User from List
|
||||||
|
async function deleteUser(user) {
|
||||||
|
let _item = null;
|
||||||
|
try {
|
||||||
|
_item = await sp.web.lists.getByTitle(birthdayListTitle).items.top(1).filter(`userAADGUID eq '${user.id}'`).get()
|
||||||
|
if (_item && _item.length > 0) {
|
||||||
|
try {
|
||||||
|
await sp.web.lists.getByTitle(birthdayListTitle).items.getById(_item[0].Id).delete();
|
||||||
|
} catch (e) {
|
||||||
|
_context.log(`Error deleting user ${user.displayName}, error: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_context.log(`Error get user ${user.displayName} to delete, error: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this method ensures that the birthdays lists exists, or if it doesn't exist create it
|
||||||
|
|
||||||
|
async function ensureBirthdaysList() {
|
||||||
|
|
||||||
|
let _web = new Web(tenantUrl);
|
||||||
|
let result = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ensureResult = await _web.lists.ensure(birthdayListTitle,
|
||||||
|
"Birthdays 2",
|
||||||
|
100,
|
||||||
|
true);
|
||||||
|
|
||||||
|
// if we've got the list
|
||||||
|
if (ensureResult.list != null) {
|
||||||
|
|
||||||
|
// if the list has just been created
|
||||||
|
if (ensureResult.created) {
|
||||||
|
// we need to add the custom fields to the list
|
||||||
|
const jobTitleFieldAddResult = await ensureResult.list.fields.addText(
|
||||||
|
"jobTitle", 100,
|
||||||
|
{ Required: false });
|
||||||
|
await jobTitleFieldAddResult.field.update({ Title: "Job Title" });
|
||||||
|
const emailFieldAddResult = await ensureResult.list.fields.addText(
|
||||||
|
"email", 100,
|
||||||
|
{ Required: true });
|
||||||
|
await emailFieldAddResult.field.update({ Title: "Email" });
|
||||||
|
const BirthdayFieldAddResult = await ensureResult.list.fields.addDateTime(
|
||||||
|
"Birthday",
|
||||||
|
);
|
||||||
|
await BirthdayFieldAddResult.field.update({ Title: "Birthday" });
|
||||||
|
const userAADGUIDFieldAddResult = await ensureResult.list.fields.addText(
|
||||||
|
"userAADGUID", 100,
|
||||||
|
{ Required: true });
|
||||||
|
await userAADGUIDFieldAddResult.field.update({ Title: "AAD ID " });
|
||||||
|
await ensureResult.list.fields.getByInternalNameOrTitle('Title').update({ Title: 'Display Name' });
|
||||||
|
// the list is ready to be used
|
||||||
|
result = true;
|
||||||
|
} else {
|
||||||
|
// the list already exists, check the fields
|
||||||
|
try {
|
||||||
|
const jobTitleField = await ensureResult.list.fields.getByInternalNameOrTitle("jobTitle").get();
|
||||||
|
const emailField = await ensureResult.list.fields.getByInternalNameOrTitle("email").get();
|
||||||
|
const BirthdayField = await ensureResult.list.fields.getByInternalNameOrTitle("Birthday").get();
|
||||||
|
const userAADGUIDField = await ensureResult.list.fields.getByInternalNameOrTitle("userAADGUID").get();
|
||||||
|
// if it is ok, then the list is ready to be used
|
||||||
|
result = true;
|
||||||
|
} catch (e) {
|
||||||
|
// if any of the fields does not exist, write exception in the _context log
|
||||||
|
_context.log(`The ${birthdayListTitle} list does not match the expected fields definition. error ${e}`);
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// if we fail to create the list, write an exception in the _context log
|
||||||
|
_context.log(`Failed to create birthdays list ${birthdayListTitle}.`);
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"IsEncrypted": false,
|
||||||
|
"Values": {
|
||||||
|
"GraphClientId": "",
|
||||||
|
"GraphClientSecret": "",
|
||||||
|
"TenantId": "",
|
||||||
|
"TenantUrl": "https://contoso.sharepoint.com",
|
||||||
|
"BirthdayListTitle": "Birthdays"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
# TimerTrigger - JavaScript
|
||||||
|
|
||||||
|
The `TimerTrigger` makes it incredibly easy to have your functions executed on a schedule. This sample demonstrates a simple use case of calling your function every 5 minutes.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
For a `TimerTrigger` to work, you provide a schedule in the form of a [cron expression](https://en.wikipedia.org/wiki/Cron#CRON_expression)(See the link for full details). A cron expression is a string with 6 separate expressions which represent a given schedule via patterns. The pattern we use to represent every 5 minutes is `0 */5 * * * *`. This, in plain text, means: "When seconds is equal to 0, minutes is divisible by 5, for any hour, day of the month, month, day of the week, or year".
|
||||||
|
|
||||||
|
## Learn more
|
||||||
|
|
||||||
|
<TODO> Documentation
|
|
@ -0,0 +1,184 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.happyBirdthay {
|
||||||
|
.documentCard {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 2px solid #eaeaea;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 320px;
|
||||||
|
min-width: 200px;
|
||||||
|
min-height: 190px;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.documentCard:hover{
|
||||||
|
border-color: #c8c8c8;
|
||||||
|
}
|
||||||
|
.documentCardWrapper {
|
||||||
|
margin-top: 15px;
|
||||||
|
min-Width: 200px;
|
||||||
|
width: 200px;
|
||||||
|
min-height: 190px;
|
||||||
|
margin-Left: 15px;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0px auto;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.today{
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
position: absolute;
|
||||||
|
top: 61px;
|
||||||
|
width: 100%;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: top;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
.centered {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
position: absolute;
|
||||||
|
top: 78px;
|
||||||
|
width: 100%;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: top;
|
||||||
|
color: #666666;
|
||||||
|
|
||||||
|
}
|
||||||
|
.personaContainer {
|
||||||
|
margin-Top: 40px;
|
||||||
|
margin-Left: 10px;
|
||||||
|
margin-Right: 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.persona{
|
||||||
|
position: absolute;
|
||||||
|
top: 137px;
|
||||||
|
max-width: 200px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.actions{
|
||||||
|
position: absolute;
|
||||||
|
top: 190px;
|
||||||
|
width: 100%;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-top-color: #f4f4f4;
|
||||||
|
border-top-width: 1.1px;
|
||||||
|
// background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
@include ms-Grid-row;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg10;
|
||||||
|
@include ms-xl8;
|
||||||
|
@include ms-xlPush2;
|
||||||
|
@include ms-lgPush1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
|
||||||
|
}
|
||||||
|
.centeredHeader {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitle {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birthdaycake {
|
||||||
|
font-Size: 20px;
|
||||||
|
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
color: #ff6f69;
|
||||||
|
// color: $ms-color-themeTertiary;
|
||||||
|
top: 60px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #eaeaea;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.displayBirthdayToday{
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
width: 100%;
|
||||||
|
//color: $ms-color-themeTertiary;
|
||||||
|
color: #ff6f69;
|
||||||
|
top: 107px;
|
||||||
|
position: absolute;
|
||||||
|
font-size: $ms-font-size-s;
|
||||||
|
|
||||||
|
}
|
||||||
|
.displayBirthday{
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
width: 100%;
|
||||||
|
color: $ms-color-themeTertiary;
|
||||||
|
top: 107px;
|
||||||
|
position: absolute;
|
||||||
|
font-size: $ms-font-size-s;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './HappyBirthdayCard.module.scss';
|
||||||
|
import { IHappyBirthdayCardProps } from './IHappyBirthdayCardProps';
|
||||||
|
import { IHappyBirthdayCardPState } from './IHappyBirthdayCardState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import { IPersonaSharedProps, Persona, PersonaSize, IPersonaProps, PersonaPresence } from 'office-ui-fabric-react/lib/Persona';
|
||||||
|
import { Image, IImageProps, ImageFit } from 'office-ui-fabric-react/lib/Image';
|
||||||
|
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||||
|
import * as strings from 'ControlStrings';
|
||||||
|
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import {
|
||||||
|
DocumentCardActions,
|
||||||
|
} from 'office-ui-fabric-react/lib/DocumentCard';
|
||||||
|
const img: string = require('../../../assets/baloons.png');
|
||||||
|
const IMG_WIDTH: number = 200;
|
||||||
|
const IMG_HEIGTH: number = 190;
|
||||||
|
|
||||||
|
export class HappyBirthdayCard extends React.Component<IHappyBirthdayCardProps, IHappyBirthdayCardPState> {
|
||||||
|
private _Persona: IPersonaSharedProps;
|
||||||
|
private _birthdayMsg: string = '';
|
||||||
|
|
||||||
|
constructor(props: IHappyBirthdayCardProps) {
|
||||||
|
super(props);
|
||||||
|
const photo: string = `/_layouts/15/userphoto.aspx?size=L&username=${this.props.userEmail}`;
|
||||||
|
console.log(photo);
|
||||||
|
this._Persona = {
|
||||||
|
imageUrl: photo ? photo : '',
|
||||||
|
imageInitials: this._getInitial(this.props.userName),
|
||||||
|
text: this.props.userName,
|
||||||
|
secondaryText: this.props.jobDescription,
|
||||||
|
tertiaryText: this.props.birthday,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isBirthdayToday: this._birthdayIsToday(this.props.birthday)
|
||||||
|
};
|
||||||
|
|
||||||
|
this._onRenderTertiaryText = this._onRenderTertiaryText.bind(this);
|
||||||
|
this._getInitial = this._getInitial.bind(this);
|
||||||
|
this._birthdayIsToday = this._birthdayIsToday.bind(this);
|
||||||
|
}
|
||||||
|
// Render
|
||||||
|
public render(): React.ReactElement<IHappyBirthdayCardProps> {
|
||||||
|
this._birthdayMsg = this.state.isBirthdayToday ? strings.HappyBirthdayMsg : strings.NextBirthdayMsg;
|
||||||
|
return (
|
||||||
|
<div className={styles.happyBirdthay}>
|
||||||
|
<div className={styles.documentCardWrapper}>
|
||||||
|
<div className={styles.documentCard}>
|
||||||
|
<Image
|
||||||
|
imageFit={ImageFit.cover}
|
||||||
|
src={img}
|
||||||
|
width={IMG_WIDTH}
|
||||||
|
height={IMG_HEIGTH}
|
||||||
|
/>
|
||||||
|
<Label className={styles.centered} >{this._birthdayMsg}</Label>
|
||||||
|
{
|
||||||
|
this.state.isBirthdayToday ?
|
||||||
|
<Label className={styles.displayBirthdayToday}>{this.props.birthday}</Label>
|
||||||
|
:
|
||||||
|
<Label className={styles.displayBirthday}>{this.props.birthday}</Label>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.isBirthdayToday ?
|
||||||
|
<Icon iconName="BirthdayCake" className={styles.birthdaycake} />
|
||||||
|
:
|
||||||
|
''
|
||||||
|
}
|
||||||
|
<div className={styles.personaContainer}>
|
||||||
|
<Persona
|
||||||
|
{...this._Persona}
|
||||||
|
size={PersonaSize.regular}
|
||||||
|
className={styles.persona}
|
||||||
|
onRenderTertiaryText={this._onRenderTertiaryText}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.actions}>
|
||||||
|
<DocumentCardActions
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
iconProps: { iconName: 'Mail' },
|
||||||
|
onClick: (ev: any) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
window.location.href = `mailto:${this.props.userEmail}?subject=${this._birthdayMsg}!`;
|
||||||
|
},
|
||||||
|
ariaLabel: 'email'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Today is Birthday ?
|
||||||
|
private _birthdayIsToday(birthday: string): boolean {
|
||||||
|
const _todayDay = moment().date();
|
||||||
|
const _todayMonth = moment().month() + 1;
|
||||||
|
const _birthdayDay = moment(birthday, 'Do MMM').date();
|
||||||
|
const _birthdayMonth = moment(birthday, 'Do MMM').month() + 1;
|
||||||
|
|
||||||
|
const _retvalue = (_todayDay === _birthdayDay && _todayMonth === _birthdayMonth) ? true : false;
|
||||||
|
|
||||||
|
return _retvalue;
|
||||||
|
}
|
||||||
|
// Get Initials
|
||||||
|
private _getInitial(userName: string): string {
|
||||||
|
const _arr = userName.split(' ');
|
||||||
|
const _initial = _arr[0].charAt(0).toUpperCase() + (_arr[1] ? _arr[1].charAt(0).toLocaleUpperCase() : "");
|
||||||
|
return _initial;
|
||||||
|
}
|
||||||
|
// Render tertiary text
|
||||||
|
private _onRenderTertiaryText = (props: IPersonaProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span className='ms-fontWeight-semibold' style={{ color: '#71afe5' }}>
|
||||||
|
{props.tertiaryText}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default HappyBirthdayCard;
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IHappyBirthdayCardProps {
|
||||||
|
userName?:string;
|
||||||
|
jobDescription?: string;
|
||||||
|
birthday: string;
|
||||||
|
userEmail:string;
|
||||||
|
congratulationsMsg?: string;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IHappyBirthdayCardPState {
|
||||||
|
|
||||||
|
isBirthdayToday: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
||||||
|
export * from './IHappyBirthdayCardProps';
|
||||||
|
export * from './IHappyBirthdayCardState';
|
||||||
|
export * from './HappyBirthdayCard';
|
|
@ -0,0 +1,8 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"BirdthayControlDefaultDay": "Today",
|
||||||
|
"HappyBirthdayMsg": "Happy Birthday!",
|
||||||
|
"NextBirthdayMsg": "Next Birthday",
|
||||||
|
"MessageNoBirthdays": "There are no birthdays for the next days."
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
declare interface IControlStrings {
|
||||||
|
BirdthayControlDefaultDay: string,
|
||||||
|
HappyBirthdayMsg: string,
|
||||||
|
NextBirthdayMsg: string,
|
||||||
|
MessageNoBirthdays: string
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'ControlStrings' {
|
||||||
|
const strings: IControlStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.happyBirthday {
|
||||||
|
.backgroundImgBallons{
|
||||||
|
text-align: center;
|
||||||
|
text-align: -webkit-center;
|
||||||
|
opacity: 0.23;
|
||||||
|
}
|
||||||
|
.documentCard {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 320px;
|
||||||
|
min-width: 200px;
|
||||||
|
min-height: 190px;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
.documentCard:hover{
|
||||||
|
border-color: #c8c8c8;
|
||||||
|
}
|
||||||
|
.documentCardWrapper {
|
||||||
|
margin-top: 15px;
|
||||||
|
min-Width: 200px;
|
||||||
|
width: 200px;
|
||||||
|
min-height: 190px;
|
||||||
|
margin-Left: 15px;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0px auto;
|
||||||
|
}
|
||||||
|
.today{
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
position: absolute;
|
||||||
|
top: 75px;
|
||||||
|
width: 100%;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: top;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
.centered {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
position: absolute;
|
||||||
|
top: 100px;
|
||||||
|
width: 100%;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: top;
|
||||||
|
}
|
||||||
|
.persona{
|
||||||
|
position: absolute;
|
||||||
|
top: 170px;
|
||||||
|
max-width: 236px;
|
||||||
|
}
|
||||||
|
.actions{
|
||||||
|
position: absolute;
|
||||||
|
top: 290px;
|
||||||
|
width: 100%;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-top-color: #f4f4f4;
|
||||||
|
border-top-width: 1.1px;
|
||||||
|
// background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
@include ms-Grid-row;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg10;
|
||||||
|
@include ms-xl8;
|
||||||
|
@include ms-xlPush2;
|
||||||
|
@include ms-lgPush1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
.centeredHeader {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitle {
|
||||||
|
@include ms-font-l;
|
||||||
|
// @include ms-fontColor-white;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
// Our button
|
||||||
|
text-decoration: none;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
// Primary Button
|
||||||
|
min-width: 80px;
|
||||||
|
background-color: $ms-color-themePrimary;
|
||||||
|
border-color: $ms-color-themePrimary;
|
||||||
|
color: $ms-color-white;
|
||||||
|
|
||||||
|
// Basic Button
|
||||||
|
outline: transparent;
|
||||||
|
position: relative;
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
font-weight: $ms-font-weight-regular;
|
||||||
|
border-width: 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: $ms-font-weight-semibold;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0 4px;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './HappyBirthday.module.scss';
|
||||||
|
import { IHappyBirthdayProps } from './IHappyBirthdayProps';
|
||||||
|
import { IHappbirthdayState } from './IHappybirthdayState';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import { IUser } from './IUser';
|
||||||
|
import HappyBirdthayCard from '../../controls/happyBirthdayCard/HappyBirthdayCard';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { Image, IImageProps, ImageFit } from 'office-ui-fabric-react/lib/Image';
|
||||||
|
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||||
|
import * as strings from 'ControlStrings';
|
||||||
|
|
||||||
|
export class HappyBirthday extends React.Component<IHappyBirthdayProps, IHappbirthdayState> {
|
||||||
|
|
||||||
|
private _showBirthdays: boolean = true;
|
||||||
|
constructor(props: IHappyBirthdayProps) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentDidMount() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps: IHappyBirthdayProps, prevState: IHappbirthdayState): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
public render(): React.ReactElement<IHappyBirthdayProps> {
|
||||||
|
return (
|
||||||
|
<div className={styles.happyBirthday}>
|
||||||
|
{
|
||||||
|
this.props.users.map((user: IUser) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<HappyBirdthayCard userName={user.userName}
|
||||||
|
jobDescription={user.jobDescription}
|
||||||
|
birthday={moment(user.birthday, ["MM-DD-YYYY", "YYYY-MM-DD", "DD/MM/YYYY", "MM/DD/YYYY"]).format('Do MMMM')}
|
||||||
|
userEmail={user.userEmail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default HappyBirthday;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { IUser } from './IUser';
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
export interface IHappyBirthdayProps {
|
||||||
|
users: IUser[];
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export interface IHappbirthdayState{
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IUser {
|
||||||
|
key: string;
|
||||||
|
userName:string;
|
||||||
|
jobDescription?: string;
|
||||||
|
birthday: string;
|
||||||
|
userEmail: string;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
||||||
|
export * from './IHappyBirthdayProps';
|
||||||
|
export * from './IHappybirthdayState';
|
||||||
|
export * from './IUser';
|
||||||
|
export * from './HappyBirthday';
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IUser {
|
||||||
|
key: string;
|
||||||
|
userName:string;
|
||||||
|
jobDescription?: string;
|
||||||
|
birthday: string;
|
||||||
|
userEmail: string;
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
import { ApplicationCustomizerContext } from "@microsoft/sp-application-base";
|
||||||
|
import { SPHttpClient, SPHttpClientResponse, MSGraphClient } from "@microsoft/sp-http";
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
export class SPService {
|
||||||
|
private graphClient: MSGraphClient = null;
|
||||||
|
private birthdayListTitle: string = "Birthdays";
|
||||||
|
constructor(private _context: WebPartContext | ApplicationCustomizerContext) {
|
||||||
|
|
||||||
|
}
|
||||||
|
// Get Profiles
|
||||||
|
public async getPBirthdays(upcommingDays: number): Promise<any[]> {
|
||||||
|
let _results, _today: string, _month: string, _day: number;
|
||||||
|
let _filter: string, _countdays: number, _f:number, _nextYearStart: string;
|
||||||
|
let _FinalDate: string;
|
||||||
|
try {
|
||||||
|
_results = null;
|
||||||
|
_today = '2000-' + moment().format('MM-DD');
|
||||||
|
_month = moment().format('MM');
|
||||||
|
_day = parseInt(moment().format('DD'));
|
||||||
|
_filter = "fields/Birthday ge '" + _today + "'";
|
||||||
|
// If we are in Dezember we have to look if there are birthday in January
|
||||||
|
// we have to build a condition to select birthday in January based on number of upcommingDays
|
||||||
|
// we can not use the year for teste , the year is always 2000.
|
||||||
|
console.log(_month);
|
||||||
|
if (_month === '12') {
|
||||||
|
_countdays = _day + upcommingDays;
|
||||||
|
_f = 0;
|
||||||
|
_nextYearStart = '2000-01-01';
|
||||||
|
_FinalDate = '2000-01-';
|
||||||
|
if ((_countdays) > 31) {
|
||||||
|
_f = _countdays - 31;
|
||||||
|
_FinalDate = _FinalDate + _f;
|
||||||
|
_filter = "fields/Birthday ge '" + _today + "' or (fields/Birthday ge '" + _nextYearStart + "' and fields/Birthday le '" + _FinalDate + "')";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.graphClient = await this._context.msGraphClientFactory.getClient();
|
||||||
|
_results = await this.graphClient.api(`sites/root/lists('${this.birthdayListTitle}')/items`)
|
||||||
|
.version('v1.0')
|
||||||
|
.expand('fields')
|
||||||
|
.top(upcommingDays)
|
||||||
|
.filter(_filter)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
return _results.value;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.dir(error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default SPService;
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "e629ef30-a9ec-4713-b39a-2cfa8b323902",
|
||||||
|
"alias": "BirthdaysWebPart",
|
||||||
|
"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,
|
||||||
|
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||||
|
"group": { "default": "Other" },
|
||||||
|
"title": { "default": "Birthdays Web Part" },
|
||||||
|
"description": { "default": "Birthdays Web Part" },
|
||||||
|
"officeFabricIconFontName": "Page",
|
||||||
|
"properties": {
|
||||||
|
"title": "Birthdays",
|
||||||
|
"numberUpcomingDays": 5
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import { PropertyFieldNumber } from '@pnp/spfx-property-controls/lib/PropertyFieldNumber';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BaseClientSideWebPart,
|
||||||
|
IPropertyPaneConfiguration,
|
||||||
|
PropertyPaneTextField
|
||||||
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
import * as strings from 'BirthdaysWebPartStrings';
|
||||||
|
import Birdthays from './components/Birthdays';
|
||||||
|
import { IBirthdaysProps } from './components/IBirthdaysProps';
|
||||||
|
import { MSGraphClient } from '@microsoft/sp-http';
|
||||||
|
|
||||||
|
export interface IBirthdaysWebPartProps {
|
||||||
|
title: string;
|
||||||
|
numberUpcomingDays: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BirthdaysWebPart extends BaseClientSideWebPart<IBirthdaysWebPartProps> {
|
||||||
|
private graphCLient: MSGraphClient;
|
||||||
|
|
||||||
|
public onInit(): Promise<void> {
|
||||||
|
|
||||||
|
return super.onInit().then(_ => {
|
||||||
|
// other init code may be present
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
const element: React.ReactElement<IBirthdaysProps > = React.createElement(
|
||||||
|
Birdthays,
|
||||||
|
{
|
||||||
|
title: this.properties.title,
|
||||||
|
numberUpcomingDays: this.properties.numberUpcomingDays,
|
||||||
|
context: this.context,
|
||||||
|
displayMode: this.displayMode,
|
||||||
|
updateProperty: (value: string) => {
|
||||||
|
this.properties.title = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: strings.PropertyPaneDescription
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: strings.BasicGroupName,
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('title', {
|
||||||
|
label: strings.DescriptionFieldLabel
|
||||||
|
}),
|
||||||
|
PropertyFieldNumber("numberUpcomingDays", {
|
||||||
|
key: "numberUpcomingDays",
|
||||||
|
label: strings.NumberUpComingDaysLabel,
|
||||||
|
description: strings.NumberUpComingDaysLabel,
|
||||||
|
value: this.properties.numberUpcomingDays,
|
||||||
|
maxValue: 10,
|
||||||
|
minValue: 5,
|
||||||
|
disabled: false
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
.happyBirthday {
|
||||||
|
.backgroundImgBallons{
|
||||||
|
text-align: center;
|
||||||
|
text-align: -webkit-center;
|
||||||
|
opacity: 0.21;
|
||||||
|
|
||||||
|
}
|
||||||
|
.documentCard {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 236px;
|
||||||
|
min-height: 300px;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
.documentCardWrapper {
|
||||||
|
margin-top: 15px;
|
||||||
|
min-Width: 238px;
|
||||||
|
width: 238px;
|
||||||
|
min-height: 300px;
|
||||||
|
|
||||||
|
|
||||||
|
margin-Left: 12px;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0px auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
.today{
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
position: absolute;
|
||||||
|
top: 75px;
|
||||||
|
width: 100%;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: top;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
.centered {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
position: absolute;
|
||||||
|
top: 100px;
|
||||||
|
width: 100%;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: top;
|
||||||
|
}
|
||||||
|
.persona{
|
||||||
|
position: absolute;
|
||||||
|
top: 170px;
|
||||||
|
max-width: 236px;
|
||||||
|
}
|
||||||
|
.actions{
|
||||||
|
position: absolute;
|
||||||
|
top: 290px;
|
||||||
|
width: 100%;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-top-color: #f4f4f4;
|
||||||
|
border-top-width: 1.1px;
|
||||||
|
// background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
@include ms-Grid-row;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
background-color: $ms-color-themeDark;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg10;
|
||||||
|
@include ms-xl8;
|
||||||
|
@include ms-xlPush2;
|
||||||
|
@include ms-lgPush1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include ms-font-xl;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
.centeredHeader {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.subTitle {
|
||||||
|
@include ms-font-m;
|
||||||
|
@include ms-fontWeight-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
@include ms-font-l;
|
||||||
|
@include ms-fontColor-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
// Our button
|
||||||
|
text-decoration: none;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
// Primary Button
|
||||||
|
min-width: 80px;
|
||||||
|
background-color: $ms-color-themePrimary;
|
||||||
|
border-color: $ms-color-themePrimary;
|
||||||
|
color: $ms-color-white;
|
||||||
|
|
||||||
|
// Basic Button
|
||||||
|
outline: transparent;
|
||||||
|
position: relative;
|
||||||
|
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
font-weight: $ms-font-weight-regular;
|
||||||
|
border-width: 0;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: $ms-font-weight-semibold;
|
||||||
|
font-size: $ms-font-size-m;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0 4px;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Birthdays.module.scss';
|
||||||
|
import { IBirthdaysProps } from './IBirthdaysProps';
|
||||||
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import { HappyBirthday, IUser } from '../../../controls/happybirthday';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { IBirthdayState } from './IBirthdaysState';
|
||||||
|
import SPService from '../../../services/SPService';
|
||||||
|
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
|
||||||
|
const imgBackgroundBallons: string = require('../../../../assets/ballonsBackgroud.png');
|
||||||
|
import { Image, IImageProps, ImageFit } from 'office-ui-fabric-react/lib/Image';
|
||||||
|
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||||
|
import * as strings from 'ControlStrings';
|
||||||
|
|
||||||
|
export default class Birthdays extends React.Component<IBirthdaysProps, IBirthdayState> {
|
||||||
|
private _users: IUser[] = [];
|
||||||
|
private _spServices: SPService;
|
||||||
|
constructor(props: IBirthdaysProps) {
|
||||||
|
super(props);
|
||||||
|
this._spServices = new SPService(this.props.context);
|
||||||
|
this.state = {
|
||||||
|
Users: [],
|
||||||
|
showBirthdays: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount(): void {
|
||||||
|
this.GetUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps: IBirthdaysProps, prevState: IBirthdayState): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
// Render
|
||||||
|
public render(): React.ReactElement<IBirthdaysProps> {
|
||||||
|
let _center: any = !this.state.showBirthdays ? "center" : "";
|
||||||
|
return (
|
||||||
|
<div className={styles.happyBirthday}
|
||||||
|
style={{ textAlign: _center }} >
|
||||||
|
<div className={styles.container}>
|
||||||
|
<WebPartTitle displayMode={this.props.displayMode}
|
||||||
|
title={this.props.title}
|
||||||
|
updateProperty={this.props.updateProperty} />
|
||||||
|
{
|
||||||
|
!this.state.showBirthdays ?
|
||||||
|
<div className={styles.backgroundImgBallons}>
|
||||||
|
<Image imageFit={ImageFit.cover}
|
||||||
|
src={imgBackgroundBallons}
|
||||||
|
width={150}
|
||||||
|
height={150}
|
||||||
|
/>
|
||||||
|
<Label className={styles.subTitle}>{strings.MessageNoBirthdays}</Label>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<HappyBirthday users={this.state.Users}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort Array of Birthdays
|
||||||
|
private SortBirthdays(users: IUser[]) {
|
||||||
|
return users.sort( (a, b) => {
|
||||||
|
if (a.birthday > b.birthday) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (a.birthday < b.birthday) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Load List Of Users
|
||||||
|
private async GetUsers() {
|
||||||
|
let _otherMonthsBirthdays: IUser[], _dezemberBirthdays: IUser[];
|
||||||
|
const listItems = await this._spServices.getPBirthdays(this.props.numberUpcomingDays);
|
||||||
|
if (listItems && listItems.length > 0) {
|
||||||
|
_otherMonthsBirthdays = [];
|
||||||
|
_dezemberBirthdays = [];
|
||||||
|
for (const item of listItems) {
|
||||||
|
this._users.push({ key: item.fields.email, userName: item.fields.Title, userEmail: item.fields.email, jobDescription: item.fields.JobTitle, birthday: item.fields.Birthday });
|
||||||
|
}
|
||||||
|
// Sort Items by Birthday MSGraph List Items API don't support ODATA orderBy
|
||||||
|
// for end of year teste and sorting
|
||||||
|
// first select all bithdays of Dezember to sort this must be the first to show
|
||||||
|
if (moment().format('MM') === '12') {
|
||||||
|
_dezemberBirthdays = this._users.filter( (v) => {
|
||||||
|
var _currentMonth = moment(v.birthday, ["MM-DD-YYYY", "YYYY-MM-DD", "DD/MM/YYYY", "MM/DD/YYYY"]).format('MM');
|
||||||
|
return (_currentMonth === '12');
|
||||||
|
});
|
||||||
|
// Sort by birthday date in Dezember month
|
||||||
|
_dezemberBirthdays = this.SortBirthdays(_dezemberBirthdays);
|
||||||
|
// select birthdays != of month 12
|
||||||
|
_otherMonthsBirthdays = this._users.filter((v) => {
|
||||||
|
var _currentMonth = moment(v.birthday, ["MM-DD-YYYY", "YYYY-MM-DD", "DD/MM/YYYY", "MM/DD/YYYY"]).format('MM');
|
||||||
|
return (_currentMonth !== '12');
|
||||||
|
});
|
||||||
|
// sort by birthday date
|
||||||
|
_otherMonthsBirthdays = this.SortBirthdays(_otherMonthsBirthdays);
|
||||||
|
// Join the 2 arrays
|
||||||
|
this._users = _dezemberBirthdays.concat(_otherMonthsBirthdays);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._users = this.SortBirthdays(this._users);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this._users=[];
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
Users: this._users,
|
||||||
|
showBirthdays: this._users.length === 0 ? false : true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||||
|
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||||
|
export interface IBirthdaysProps {
|
||||||
|
title: string;
|
||||||
|
numberUpcomingDays: number;
|
||||||
|
context: WebPartContext;
|
||||||
|
displayMode: DisplayMode;
|
||||||
|
updateProperty: (value: string) => void;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import {IUser } from '../../../services/IUser';
|
||||||
|
export interface IBirthdayState{
|
||||||
|
Users : IUser[] ;
|
||||||
|
showBirthdays: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"PropertyPaneDescription": "Happy Brithday",
|
||||||
|
"BasicGroupName": "Properties",
|
||||||
|
"DescriptionFieldLabel": "Title",
|
||||||
|
"NumberUpComingDaysLabel": 'Number of upcomming birthdays'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
declare interface IBirthdayWebPartStrings {
|
||||||
|
PropertyPaneDescription: string;
|
||||||
|
BasicGroupName: string;
|
||||||
|
DescriptionFieldLabel: string;
|
||||||
|
NumberUpComingDaysLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'BirthdaysWebPartStrings' {
|
||||||
|
const strings: IBirthdayWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
|
||||||
|
"manifestVersion": "1.2",
|
||||||
|
"packageName": "HappyBirdthay",
|
||||||
|
"id": "e629ef30-a9ec-4713-b39a-2cfa8b323902",
|
||||||
|
"version": "0.1",
|
||||||
|
"developer": {
|
||||||
|
"name": "SPFx + Teams Dev",
|
||||||
|
"websiteUrl": "https://products.office.com/en-us/sharepoint/collaboration",
|
||||||
|
"privacyUrl": "https://privacy.microsoft.com/en-us/privacystatement",
|
||||||
|
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"short": "HappyBirdthay"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"short": "HappyBirdthay description",
|
||||||
|
"full": "HappyBirdthay description"
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"outline": "tab20x20.png",
|
||||||
|
"color": "tab96x96.png"
|
||||||
|
},
|
||||||
|
"accentColor": "#004578",
|
||||||
|
"configurableTabs": [
|
||||||
|
{
|
||||||
|
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=e629ef30-a9ec-4713-b39a-2cfa8b323902",
|
||||||
|
"canUpdateConfiguration": false,
|
||||||
|
"scopes": [
|
||||||
|
"team"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validDomains": [
|
||||||
|
"*.login.microsoftonline.com",
|
||||||
|
"*.sharepoint.com",
|
||||||
|
"*.sharepoint-df.com",
|
||||||
|
"spoppe-a.akamaihd.net",
|
||||||
|
"spoprod-a.akamaihd.net",
|
||||||
|
"resourceseng.blob.core.windows.net",
|
||||||
|
"msft.spoppe.com"
|
||||||
|
],
|
||||||
|
"webApplicationInfo": {
|
||||||
|
"resource": "https://{teamSiteDomain}",
|
||||||
|
"id": "00000003-0000-0ff1-ce00-000000000000"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 933 B |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "lib",
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./node_modules/@microsoft"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es5",
|
||||||
|
"dom",
|
||||||
|
"es2015.collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||||
|
"rules": {
|
||||||
|
"class-name": false,
|
||||||
|
"export-name": false,
|
||||||
|
"forin": false,
|
||||||
|
"label-position": false,
|
||||||
|
"member-access": true,
|
||||||
|
"no-arg": false,
|
||||||
|
"no-console": false,
|
||||||
|
"no-construct": false,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-eval": false,
|
||||||
|
"no-function-expression": true,
|
||||||
|
"no-internal-module": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-unnecessary-semicolons": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"no-with-statement": true,
|
||||||
|
"semicolon": true,
|
||||||
|
"trailing-comma": false,
|
||||||
|
"typedef": false,
|
||||||
|
"typedef-whitespace": false,
|
||||||
|
"use-named-parameter": true,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,4 +26,28 @@
|
||||||
|
|
||||||
.hoverIcon {
|
.hoverIcon {
|
||||||
color: '[theme:themeDarker, default:#0078d7]';
|
color: '[theme:themeDarker, default:#0078d7]';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
|
||||||
|
margin-top: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
* + * {
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, a:visited, a:focus, a:link, a:active {
|
||||||
|
color: '[theme:themePrimary, default:#0078d7]';
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not(:empty):after {
|
||||||
|
content: ",";
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not(:empty):last-child:after {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -39,79 +39,80 @@ abstract class BaseTemplateService {
|
||||||
public static getListDefaultTemplate(): string {
|
public static getListDefaultTemplate(): string {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
.template_listItem {
|
.template_listItem {
|
||||||
display:flex;
|
display:flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template_listItem img.img-preview {
|
.template_listItem img.img-preview {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
display: block;
|
display: block;
|
||||||
height: auto;
|
height: auto;
|
||||||
transition: .5s ease;
|
transition: .5s ease;
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template_result {
|
.template_result {
|
||||||
display: flex;
|
display: flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template_listItem iframe, .template_listItem .video-js {
|
.template_listItem iframe, .template_listItem .video-js {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template_contentContainer {
|
.template_contentContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template_previewContainer {
|
.template_previewContainer {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Width for the documents and videos preview */
|
|
||||||
.videoPreview, .iframePreview {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.template_icon {
|
/* Width for the documents and videos preview */
|
||||||
height: 32px;
|
.videoPreview, .iframePreview {
|
||||||
margin-right: 15px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.template_icon {
|
||||||
|
height: 32px;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover {
|
||||||
|
transition: .5s ease;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-container:hover img {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-container:hover .hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.hover {
|
|
||||||
transition: .5s ease;
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
-ms-transform: translate(-50%, -50%);
|
|
||||||
text-align: center;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-container:hover img {
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-container:hover .hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<div class="template_root">
|
<div class="template_root">
|
||||||
{{#if showResultsCount}}
|
{{#if showResultsCount}}
|
||||||
|
@ -145,7 +146,15 @@ abstract class BaseTemplateService {
|
||||||
<div class="template_contentContainer">
|
<div class="template_contentContainer">
|
||||||
<span class=""><a href="{{getUrl item}}">{{Title}}</a></span>
|
<span class=""><a href="{{getUrl item}}">{{Title}}</a></span>
|
||||||
<span class="">{{getSummary HitHighlightedSummary}}</span>
|
<span class="">{{getSummary HitHighlightedSummary}}</span>
|
||||||
<span class=""><span>{{getDate Created "LL"}}</span></span>
|
<span class=""><span>{{getDate Created "LL"}}</span></span>
|
||||||
|
<div class="${templateStyles.tags}">
|
||||||
|
{{#if owstaxidmetadataalltagsinfo}}
|
||||||
|
<i class="ms-Icon ms-Icon--Tag" aria-hidden="true"></i>
|
||||||
|
{{#each (split owstaxidmetadataalltagsinfo ";") as |tag| }}
|
||||||
|
<a href="#owstaxidmetadataalltagsinfo:{{getLabel tag}}">{{getLabel tag}}</a>
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="template_previewContainer ms-hiddenSm">
|
<div class="template_previewContainer ms-hiddenSm">
|
||||||
|
@ -384,6 +393,19 @@ abstract class BaseTemplateService {
|
||||||
}
|
}
|
||||||
return result.length;
|
return result.length;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Return the text label from amn'owstaxid_' type managed property
|
||||||
|
// <p>{{getLabel "L0|#045686734-5215-4aad-bed7-8c3f0dbb61fc|Document"}}</p>
|
||||||
|
Handlebars.registerHelper("getLabel", (owsTaxIdValue: string) => {
|
||||||
|
|
||||||
|
let termLabel = owsTaxIdValue;
|
||||||
|
const matches = /L0\|#.+\|(.*)/.exec(owsTaxIdValue);
|
||||||
|
if (matches) {
|
||||||
|
termLabel = matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return termLabel;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -44,9 +44,13 @@
|
||||||
{
|
{
|
||||||
"refinerName": "Size",
|
"refinerName": "Size",
|
||||||
"displayValue": "Size of the file"
|
"displayValue": "Size of the file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refinerName": "owstaxidmetadataalltagsinfo",
|
||||||
|
"displayValue": "Tags"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"selectedProperties": "Title,Path,Created,Filename,SiteLogo,PreviewUrl,PictureThumbnailURL,ServerRedirectedPreviewURL,ServerRedirectedURL,HitHighlightedSummary,FileType,contentclass,ServerRedirectedEmbedURL,DefaultEncodingURL",
|
"selectedProperties": "Title,Path,Created,Filename,SiteLogo,PreviewUrl,PictureThumbnailURL,ServerRedirectedPreviewURL,ServerRedirectedURL,HitHighlightedSummary,FileType,contentclass,ServerRedirectedEmbedURL,DefaultEncodingURL,owstaxidmetadataalltagsinfo",
|
||||||
"enableQueryRules": false,
|
"enableQueryRules": false,
|
||||||
"maxResultsCount": 10,
|
"maxResultsCount": 10,
|
||||||
"showBlank": true,
|
"showBlank": true,
|
||||||
|
|
|
@ -246,7 +246,11 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
{
|
{
|
||||||
refinerName: "Size",
|
refinerName: "Size",
|
||||||
displayValue: "Size of the file"
|
displayValue: "Size of the file"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
refinerName: "owstaxidmetadataalltagsinfo",
|
||||||
|
displayValue: "Tags"
|
||||||
|
}
|
||||||
];
|
];
|
||||||
this.properties.sortList = Array.isArray(this.properties.sortList) ? this.properties.sortList : [
|
this.properties.sortList = Array.isArray(this.properties.sortList) ? this.properties.sortList : [
|
||||||
{
|
{
|
||||||
|
@ -259,7 +263,7 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
this.properties.sortableFields = Array.isArray(this.properties.sortableFields) ? this.properties.sortableFields : [];
|
this.properties.sortableFields = Array.isArray(this.properties.sortableFields) ? this.properties.sortableFields : [];
|
||||||
this.properties.selectedProperties = this.properties.selectedProperties ? this.properties.selectedProperties : "Title,Path,Created,Filename,SiteLogo,PreviewUrl,PictureThumbnailURL,ServerRedirectedPreviewURL,ServerRedirectedURL,HitHighlightedSummary,FileType,contentclass,ServerRedirectedEmbedURL,DefaultEncodingURL";
|
this.properties.selectedProperties = this.properties.selectedProperties ? this.properties.selectedProperties : "Title,Path,Created,Filename,SiteLogo,PreviewUrl,PictureThumbnailURL,ServerRedirectedPreviewURL,ServerRedirectedURL,HitHighlightedSummary,FileType,contentclass,ServerRedirectedEmbedURL,DefaultEncodingURL,owstaxidmetadataalltagsinfo";
|
||||||
this.properties.maxResultsCount = this.properties.maxResultsCount ? this.properties.maxResultsCount : 10;
|
this.properties.maxResultsCount = this.properties.maxResultsCount ? this.properties.maxResultsCount : 10;
|
||||||
this.properties.resultTypes = Array.isArray(this.properties.resultTypes) ? this.properties.resultTypes : [];
|
this.properties.resultTypes = Array.isArray(this.properties.resultTypes) ? this.properties.resultTypes : [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ export interface ISuggestedTeamMembersProps {
|
||||||
|
|
||||||
export interface ISuggestedTeamMembersState {
|
export interface ISuggestedTeamMembersState {
|
||||||
people: IPerson[];
|
people: IPerson[];
|
||||||
|
userIsGroupOwner: boolean;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPerson {
|
export interface IPerson {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import styles from './SuggestedTeamMembers.module.scss';
|
||||||
import { IPersonaProps, Persona, PersonaSize } from 'office-ui-fabric-react/lib/Persona';
|
import { IPersonaProps, Persona, PersonaSize } from 'office-ui-fabric-react/lib/Persona';
|
||||||
import { IPersonaWithMenu } from 'office-ui-fabric-react/lib/components/pickers/PeoplePicker/PeoplePickerItems/PeoplePickerItem.types';
|
import { IPersonaWithMenu } from 'office-ui-fabric-react/lib/components/pickers/PeoplePicker/PeoplePickerItems/PeoplePickerItem.types';
|
||||||
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CompactPeoplePicker,
|
CompactPeoplePicker,
|
||||||
|
@ -26,7 +27,7 @@ export interface IMembersPickerProps {
|
||||||
export interface IMembersPickerState {
|
export interface IMembersPickerState {
|
||||||
peopleList: IPersonaProps[];
|
peopleList: IPersonaProps[];
|
||||||
currentSelectedItems?: IPersonaProps[];
|
currentSelectedItems?: IPersonaProps[];
|
||||||
resultAddMembers: string;
|
resultAddMembers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const suggestionProps: IBasePickerSuggestionsProps = {
|
const suggestionProps: IBasePickerSuggestionsProps = {
|
||||||
|
@ -60,7 +61,7 @@ export default class MembersPicker extends React.Component<IMembersPickerProps,
|
||||||
this.state = {
|
this.state = {
|
||||||
peopleList: peopleList,
|
peopleList: peopleList,
|
||||||
currentSelectedItems: [],
|
currentSelectedItems: [],
|
||||||
resultAddMembers: null
|
resultAddMembers: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +161,15 @@ export default class MembersPicker extends React.Component<IMembersPickerProps,
|
||||||
onClick={() => { this._addGroupMembers(); }}
|
onClick={() => { this._addGroupMembers(); }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Label>{this.state.resultAddMembers}</Label>
|
{
|
||||||
|
this.state.resultAddMembers.map(s => {
|
||||||
|
let type: MessageBarType = MessageBarType.info;
|
||||||
|
if (s.indexOf("Error") >= 0) {
|
||||||
|
type = MessageBarType.error;
|
||||||
|
}
|
||||||
|
return <MessageBar messageBarType={type} isMultiline={false}>{s}</MessageBar>;
|
||||||
|
})
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -190,12 +199,21 @@ export default class MembersPicker extends React.Component<IMembersPickerProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
var response: GraphHttpClientResponse = await this.props.graphHttpClient.post('v1.0/$batch', GraphHttpClient.configurations.v1, options);
|
var response: GraphHttpClientResponse = await this.props.graphHttpClient.post('v1.0/$batch', GraphHttpClient.configurations.v1, options);
|
||||||
var responseJson: string = await response.json();
|
var responseJson: any = await response.json();
|
||||||
|
|
||||||
console.log(responseJson);
|
console.log(responseJson);
|
||||||
|
|
||||||
|
let responsesInfo: string[] = [];
|
||||||
|
responseJson.responses.forEach((r: any) => {
|
||||||
|
if (r.status === 204) {
|
||||||
|
responsesInfo.push(`User ${r.id} added succesfuly`);
|
||||||
|
} else {
|
||||||
|
responsesInfo.push(`Error adding User ${r.id}. Maybe the user is already a member`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
resultAddMembers: "Members added to the group successfully"
|
resultAddMembers: responsesInfo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { DefaultButton, PrimaryButton, IButtonProps, ActionButton } from 'office
|
||||||
import { Label } from 'office-ui-fabric-react/lib/Label';
|
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||||
import { Guid } from '@microsoft/sp-core-library';
|
import { Guid } from '@microsoft/sp-core-library';
|
||||||
import MembersPicker from './MembersPicker';
|
import MembersPicker from './MembersPicker';
|
||||||
|
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||||
|
|
||||||
export default class SuggestedTeamMembers extends React.Component<ISuggestedTeamMembersProps, ISuggestedTeamMembersState> {
|
export default class SuggestedTeamMembers extends React.Component<ISuggestedTeamMembersProps, ISuggestedTeamMembersState> {
|
||||||
|
|
||||||
|
@ -15,18 +16,41 @@ export default class SuggestedTeamMembers extends React.Component<ISuggestedTeam
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
people: []
|
loading: true,
|
||||||
|
people: [],
|
||||||
|
userIsGroupOwner: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
this._getMyPeople().then(people => {
|
this._userIsOwner().then(isOwner => {
|
||||||
this.setState({
|
if (!isOwner) {
|
||||||
people: people
|
this.setState({
|
||||||
});
|
loading: false,
|
||||||
|
userIsGroupOwner: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._getMyPeople().then(people => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
people: people,
|
||||||
|
userIsGroupOwner: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _userIsOwner(): Promise<boolean> {
|
||||||
|
const query: string = `v1.0/me/ownedObjects?$filter=id eq '${this.props.groupId}'`;
|
||||||
|
|
||||||
|
const response: GraphHttpClientResponse = await this.props.graphHttpClient.get(
|
||||||
|
query,
|
||||||
|
GraphHttpClient.configurations.v1);
|
||||||
|
|
||||||
|
return response.ok;
|
||||||
|
}
|
||||||
|
|
||||||
private async _getMyPeople(): Promise<IPerson[]> {
|
private async _getMyPeople(): Promise<IPerson[]> {
|
||||||
const query: string = "v1.0/me/people?$filter=personType/class eq 'Person'";
|
const query: string = "v1.0/me/people?$filter=personType/class eq 'Person'";
|
||||||
|
|
||||||
|
@ -51,17 +75,29 @@ export default class SuggestedTeamMembers extends React.Component<ISuggestedTeam
|
||||||
|
|
||||||
public render(): React.ReactElement<ISuggestedTeamMembersProps> {
|
public render(): React.ReactElement<ISuggestedTeamMembersProps> {
|
||||||
|
|
||||||
if (this.state.people == null || this.state.people.length === 0) {
|
let title: string = '';
|
||||||
|
if (this.state.loading) {
|
||||||
return <div>Loading data...</div>;
|
return <div>Loading data...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.state.userIsGroupOwner) {
|
||||||
|
return <MessageBar messageBarType={MessageBarType.error} isMultiline={false}>You are not Owner of this Group</MessageBar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.teamsContext) {
|
||||||
|
title = 'Team: ' + this.props.teamsContext.teamName;
|
||||||
|
} else {
|
||||||
|
title = 'Group: ' + this.props.groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerTitle = "These are suggested members to add to the " + title + "...";
|
||||||
return <div className={styles.suggestedTeamMembers}>
|
return <div className={styles.suggestedTeamMembers}>
|
||||||
<p>These are suggested members to add to the group...</p>
|
<p>{headerTitle}</p>
|
||||||
<MembersPicker
|
<MembersPicker
|
||||||
people = {this.state.people}
|
people = {this.state.people}
|
||||||
groupId = {this.props.groupId}
|
groupId = {this.props.groupId}
|
||||||
graphHttpClient={this.props.graphHttpClient}
|
graphHttpClient={this.props.graphHttpClient}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue