React-Birthdays Web Part (#716)
* Birthday Web Part * commit change * commit chnages * commit changes * commit changes * commit changes * commit changes * commit changes * Update index.js
This commit is contained in:
parent
6b55641f04
commit
c4f71bc158
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue