Extract functions
This commit is contained in:
parent
79666792a1
commit
4921e3b028
|
@ -79,7 +79,7 @@ module.exports = {
|
||||||
// This rule should be suppressed only in very special cases such as JSON.stringify()
|
// This rule should be suppressed only in very special cases such as JSON.stringify()
|
||||||
// where the type really can be anything. Even if the type is flexible, another type
|
// where the type really can be anything. Even if the type is flexible, another type
|
||||||
// may be more appropriate such as "unknown", "{}", or "Record<k,V>".
|
// may be more appropriate such as "unknown", "{}", or "Record<k,V>".
|
||||||
'@typescript-eslint/no-explicit-any': 1,
|
'@typescript-eslint/no-explicit-any': 0,
|
||||||
// RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch()
|
// RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch()
|
||||||
// handler. Thus wherever a Promise arises, the code must either append a catch handler,
|
// handler. Thus wherever a Promise arises, the code must either append a catch handler,
|
||||||
// or else return the object to a caller (who assumes this responsibility). Unterminated
|
// or else return the object to a caller (who assumes this responsibility). Unterminated
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
"solution": {
|
"solution": {
|
||||||
"name": "react-birthdays-per-month-client-side-solution",
|
"name": "React Birthdays Per Month",
|
||||||
"id": "e4cd97a3-4515-42cd-8a12-f765eaf60caa",
|
"id": "e4cd97a3-4515-42cd-8a12-f765eaf60caa",
|
||||||
"version": "1.0.0.0",
|
"version": "1.1.0.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"skipFeatureDeployment": true,
|
"skipFeatureDeployment": true,
|
||||||
"isDomainIsolated": false,
|
"isDomainIsolated": false,
|
||||||
|
@ -35,6 +35,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"zippedPackage": "solution/react-birthdays-per-month.sppkg"
|
"zippedPackage": "solution/React Birthdays Per Month.sppkg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.15.2",
|
"@microsoft/sp-office-ui-fabric-core": "1.15.2",
|
||||||
"@microsoft/sp-property-pane": "1.15.2",
|
"@microsoft/sp-property-pane": "1.15.2",
|
||||||
"@microsoft/sp-webpart-base": "1.15.2",
|
"@microsoft/sp-webpart-base": "1.15.2",
|
||||||
|
"@pnp/sp": "^3.8.0",
|
||||||
|
"date-fns": "^2.29.3",
|
||||||
"office-ui-fabric-react": "7.185.7",
|
"office-ui-fabric-react": "7.185.7",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
|
|
|
@ -11,12 +11,14 @@ specifiers:
|
||||||
'@microsoft/sp-office-ui-fabric-core': 1.15.2
|
'@microsoft/sp-office-ui-fabric-core': 1.15.2
|
||||||
'@microsoft/sp-property-pane': 1.15.2
|
'@microsoft/sp-property-pane': 1.15.2
|
||||||
'@microsoft/sp-webpart-base': 1.15.2
|
'@microsoft/sp-webpart-base': 1.15.2
|
||||||
|
'@pnp/sp': ^3.8.0
|
||||||
'@rushstack/eslint-config': 2.5.1
|
'@rushstack/eslint-config': 2.5.1
|
||||||
'@types/react': 16.9.51
|
'@types/react': 16.9.51
|
||||||
'@types/react-dom': 16.9.8
|
'@types/react-dom': 16.9.8
|
||||||
'@types/webpack-env': ~1.15.2
|
'@types/webpack-env': ~1.15.2
|
||||||
ajv: ^6.12.5
|
ajv: ^6.12.5
|
||||||
autoprefixer: ^10.4.13
|
autoprefixer: ^10.4.13
|
||||||
|
date-fns: ^2.29.3
|
||||||
eslint-plugin-react-hooks: 4.3.0
|
eslint-plugin-react-hooks: 4.3.0
|
||||||
gulp: 4.0.2
|
gulp: 4.0.2
|
||||||
gulp-postcss: ^9.0.1
|
gulp-postcss: ^9.0.1
|
||||||
|
@ -36,6 +38,8 @@ dependencies:
|
||||||
'@microsoft/sp-office-ui-fabric-core': 1.15.2
|
'@microsoft/sp-office-ui-fabric-core': 1.15.2
|
||||||
'@microsoft/sp-property-pane': 1.15.2_7ombvvupg4tnmt4iqt5m47i6cu
|
'@microsoft/sp-property-pane': 1.15.2_7ombvvupg4tnmt4iqt5m47i6cu
|
||||||
'@microsoft/sp-webpart-base': 1.15.2_7ombvvupg4tnmt4iqt5m47i6cu
|
'@microsoft/sp-webpart-base': 1.15.2_7ombvvupg4tnmt4iqt5m47i6cu
|
||||||
|
'@pnp/sp': 3.8.0
|
||||||
|
date-fns: 2.29.3
|
||||||
office-ui-fabric-react: 7.185.7_24igt2r6uynb67fv3burekl4py
|
office-ui-fabric-react: 7.185.7_24igt2r6uynb67fv3burekl4py
|
||||||
react: 16.13.1
|
react: 16.13.1
|
||||||
react-dom: 16.13.1_react@16.13.1
|
react-dom: 16.13.1_react@16.13.1
|
||||||
|
@ -76,7 +80,7 @@ packages:
|
||||||
resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==}
|
resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@azure/core-asynciterator-polyfill/1.0.2:
|
/@azure/core-asynciterator-polyfill/1.0.2:
|
||||||
|
@ -89,7 +93,7 @@ packages:
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@azure/abort-controller': 1.1.0
|
'@azure/abort-controller': 1.1.0
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@azure/core-http/1.2.6:
|
/@azure/core-http/1.2.6:
|
||||||
|
@ -107,7 +111,7 @@ packages:
|
||||||
node-fetch: 2.6.7
|
node-fetch: 2.6.7
|
||||||
process: 0.11.10
|
process: 0.11.10
|
||||||
tough-cookie: 4.1.2
|
tough-cookie: 4.1.2
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
tunnel: 0.0.6
|
tunnel: 0.0.6
|
||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
xml2js: 0.4.23
|
xml2js: 0.4.23
|
||||||
|
@ -123,7 +127,7 @@ packages:
|
||||||
'@azure/core-http': 1.2.6
|
'@azure/core-http': 1.2.6
|
||||||
'@azure/core-tracing': 1.0.0-preview.11
|
'@azure/core-tracing': 1.0.0-preview.11
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -132,7 +136,7 @@ packages:
|
||||||
resolution: {integrity: sha512-H6Tg9eBm0brHqLy0OSAGzxIh1t4UL8eZVrSUMJ60Ra9cwq2pOskFqVpz2pYoHDsBY1jZ4V/P8LRGb5D5pmC6rg==}
|
resolution: {integrity: sha512-H6Tg9eBm0brHqLy0OSAGzxIh1t4UL8eZVrSUMJ60Ra9cwq2pOskFqVpz2pYoHDsBY1jZ4V/P8LRGb5D5pmC6rg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@azure/core-tracing/1.0.0-preview.11:
|
/@azure/core-tracing/1.0.0-preview.11:
|
||||||
|
@ -141,7 +145,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@opencensus/web-types': 0.0.7
|
'@opencensus/web-types': 0.0.7
|
||||||
'@opentelemetry/api': 1.0.0-rc.0
|
'@opentelemetry/api': 1.0.0-rc.0
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@azure/core-tracing/1.0.0-preview.7:
|
/@azure/core-tracing/1.0.0-preview.7:
|
||||||
|
@ -158,7 +162,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@opencensus/web-types': 0.0.7
|
'@opencensus/web-types': 0.0.7
|
||||||
'@opentelemetry/api': 0.10.2
|
'@opentelemetry/api': 0.10.2
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@azure/identity/1.0.3:
|
/@azure/identity/1.0.3:
|
||||||
|
@ -182,7 +186,7 @@ packages:
|
||||||
resolution: {integrity: sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==}
|
resolution: {integrity: sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@azure/msal-browser/2.22.0:
|
/@azure/msal-browser/2.22.0:
|
||||||
|
@ -208,7 +212,7 @@ packages:
|
||||||
'@azure/logger': 1.0.3
|
'@azure/logger': 1.0.3
|
||||||
'@opentelemetry/api': 0.10.2
|
'@opentelemetry/api': 0.10.2
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -225,7 +229,7 @@ packages:
|
||||||
'@azure/logger': 1.0.3
|
'@azure/logger': 1.0.3
|
||||||
'@opentelemetry/api': 0.10.2
|
'@opentelemetry/api': 0.10.2
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1126,7 +1130,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@azure/msal-browser': 2.22.0
|
'@azure/msal-browser': 2.22.0
|
||||||
'@babel/runtime': 7.20.0
|
'@babel/runtime': 7.20.0
|
||||||
tslib: 2.3.1
|
tslib: 2.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@microsoft/office-ui-fabric-react-bundle/1.15.2_7ombvvupg4tnmt4iqt5m47i6cu:
|
/@microsoft/office-ui-fabric-react-bundle/1.15.2_7ombvvupg4tnmt4iqt5m47i6cu:
|
||||||
|
@ -1790,6 +1794,31 @@ packages:
|
||||||
webpack-dev-server: 3.11.2_jtacdaqalodjhudd6qhs6dld5q
|
webpack-dev-server: 3.11.2_jtacdaqalodjhudd6qhs6dld5q
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@pnp/core/3.8.0:
|
||||||
|
resolution: {integrity: sha512-xPUohlAxbLate6yjR0+ajE29PJouhLqKW6iO37Xl3npT5IdC3C8k/W/RTvXEu1xegHAM+LVYQ3/OhKr+zxf6tw==}
|
||||||
|
engines: {node: '>=14.15.1'}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@pnp/queryable/3.8.0:
|
||||||
|
resolution: {integrity: sha512-W2pX05FmIuFaQTNqcbrblgFjd9WVc0W4ElKAf5u4vMp0zlPidtsybkME6YC0o4k9+4LgTT9HZzCEY3ovuxhZ4g==}
|
||||||
|
engines: {node: '>=14.15.1'}
|
||||||
|
dependencies:
|
||||||
|
'@pnp/core': 3.8.0
|
||||||
|
tslib: 2.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@pnp/sp/3.8.0:
|
||||||
|
resolution: {integrity: sha512-8mfvaCODbdfud3lJVRWbVWIQ4SlbN3sNBhSEDq4fHnC/SNSn21V2ADOR//LqYcovdPQU3AKTlqrorItoSnK8/g==}
|
||||||
|
engines: {node: '>=14.15.1'}
|
||||||
|
requiresBuild: true
|
||||||
|
dependencies:
|
||||||
|
'@pnp/core': 3.8.0
|
||||||
|
'@pnp/queryable': 3.8.0
|
||||||
|
tslib: 2.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@pnpm/error/1.4.0:
|
/@pnpm/error/1.4.0:
|
||||||
resolution: {integrity: sha512-vxkRrkneBPVmP23kyjnYwVOtipwlSl6UfL+h+Xa3TrABJTz5rYBXemlTsU5BzST8U4pD7YDkTb3SQu+MMuIDKA==}
|
resolution: {integrity: sha512-vxkRrkneBPVmP23kyjnYwVOtipwlSl6UfL+h+Xa3TrABJTz5rYBXemlTsU5BzST8U4pD7YDkTb3SQu+MMuIDKA==}
|
||||||
engines: {node: '>=10.16'}
|
engines: {node: '>=10.16'}
|
||||||
|
@ -5490,6 +5519,11 @@ packages:
|
||||||
whatwg-url: 7.1.0
|
whatwg-url: 7.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/date-fns/2.29.3:
|
||||||
|
resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
|
||||||
|
engines: {node: '>=0.11'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/dateformat/1.0.12:
|
/dateformat/1.0.12:
|
||||||
resolution: {integrity: sha512-5sFRfAAmbHdIts+eKjR9kYJoF0ViCMVX9yqLu5A7S/v+nd077KgCITOMiirmyCBiZpKLDXbBOkYm6tu7rX/TKg==}
|
resolution: {integrity: sha512-5sFRfAAmbHdIts+eKjR9kYJoF0ViCMVX9yqLu5A7S/v+nd077KgCITOMiirmyCBiZpKLDXbBOkYm6tu7rX/TKg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -14185,6 +14219,10 @@ packages:
|
||||||
|
|
||||||
/tslib/2.3.1:
|
/tslib/2.3.1:
|
||||||
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
|
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/tslib/2.4.0:
|
||||||
|
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
||||||
|
|
||||||
/tsutils/3.21.0_typescript@4.5.5:
|
/tsutils/3.21.0_typescript@4.5.5:
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { User } from "./User";
|
||||||
|
|
||||||
|
export interface BirthdaysInMonth {
|
||||||
|
title: string;
|
||||||
|
users: Array<User>;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
month: string;
|
||||||
|
monthIndex: number;
|
||||||
|
date: number;
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { SPFI } from "@pnp/sp";
|
||||||
|
import "@pnp/sp/webs";
|
||||||
|
import "@pnp/sp/lists";
|
||||||
|
import "@pnp/sp/items";
|
||||||
|
import { BirthdaysInMonth } from "../models/BirthdaysInMonth";
|
||||||
|
import { User } from "../models/User";
|
||||||
|
import { sortBy } from "@microsoft/sp-lodash-subset";
|
||||||
|
|
||||||
|
export class SharePointService {
|
||||||
|
private readonly _spfi: SPFI;
|
||||||
|
|
||||||
|
constructor(spfi: SPFI) {
|
||||||
|
this._spfi = spfi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async GetBirthdays(): Promise<Array<BirthdaysInMonth>> {
|
||||||
|
const items: Array<any> = await this._spfi.web.lists
|
||||||
|
.getByTitle("Birthdays")
|
||||||
|
.items.expand("Employee")
|
||||||
|
.select("ID,Month,Date,Employee/Title,Employee/UserName")();
|
||||||
|
|
||||||
|
return this.ProcessData(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GenerateMonths(): Array<BirthdaysInMonth> {
|
||||||
|
const months: Array<BirthdaysInMonth> = [];
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
const today = new Date();
|
||||||
|
today.setMonth(today.getMonth() + i);
|
||||||
|
months.push({
|
||||||
|
title: today.toLocaleString("en-AU", { month: "long" }),
|
||||||
|
users: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return months;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GetMonthIndex(month: string): number {
|
||||||
|
switch (month) {
|
||||||
|
case "January":
|
||||||
|
return 0;
|
||||||
|
case "February":
|
||||||
|
return 1;
|
||||||
|
case "March":
|
||||||
|
return 2;
|
||||||
|
case "April":
|
||||||
|
return 3;
|
||||||
|
case "May":
|
||||||
|
return 4;
|
||||||
|
case "June":
|
||||||
|
return 5;
|
||||||
|
case "July":
|
||||||
|
return 6;
|
||||||
|
case "August":
|
||||||
|
return 7;
|
||||||
|
case "September":
|
||||||
|
return 8;
|
||||||
|
case "October":
|
||||||
|
return 9;
|
||||||
|
case "November":
|
||||||
|
return 10;
|
||||||
|
case "December":
|
||||||
|
return 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcessData(items: any): Array<BirthdaysInMonth> {
|
||||||
|
const months = this.GenerateMonths();
|
||||||
|
for (let i = 0; i < months.length; i++) {
|
||||||
|
const month = months[i];
|
||||||
|
month.users = sortBy(
|
||||||
|
items
|
||||||
|
.filter((item: any) => item.Month === month.title)
|
||||||
|
.map(
|
||||||
|
(item: any): User => ({
|
||||||
|
id: item.ID,
|
||||||
|
name: item.Employee.Title,
|
||||||
|
email: item.Employee.UserName,
|
||||||
|
date: item.Date,
|
||||||
|
month: item.Month,
|
||||||
|
monthIndex: this.GetMonthIndex(item.Month),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
"date"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return months;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,11 +18,10 @@
|
||||||
"preconfiguredEntries": [{
|
"preconfiguredEntries": [{
|
||||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced
|
||||||
"group": { "default": "Advanced" },
|
"group": { "default": "Advanced" },
|
||||||
"title": { "default": "BirthdaysPerMonth" },
|
"title": { "default": "Birthdays Per Month" },
|
||||||
"description": { "default": "BirthdaysPerMonth description" },
|
"description": { "default": "" },
|
||||||
"officeFabricIconFontName": "Page",
|
"officeFabricIconFontName": "BirthdayCake",
|
||||||
"properties": {
|
"properties": {
|
||||||
"description": "BirthdaysPerMonth"
|
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
PropertyPaneTextField,
|
PropertyPaneTextField,
|
||||||
} from "@microsoft/sp-property-pane";
|
} from "@microsoft/sp-property-pane";
|
||||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||||
|
import { spfi, SPFx, SPFI } from "@pnp/sp";
|
||||||
|
|
||||||
import * as strings from "BirthdaysPerMonthWebPartStrings";
|
import * as strings from "BirthdaysPerMonthWebPartStrings";
|
||||||
import "../../../assets/dist/tailwind.css";
|
import "../../../assets/dist/tailwind.css";
|
||||||
|
@ -13,29 +14,31 @@ import {
|
||||||
BirthdaysPerMonth,
|
BirthdaysPerMonth,
|
||||||
IBirthdaysPerMonthProps,
|
IBirthdaysPerMonthProps,
|
||||||
} from "./components/BirthdaysPerMonth";
|
} from "./components/BirthdaysPerMonth";
|
||||||
|
import { BirthdaysInMonth } from "../../models/BirthdaysInMonth";
|
||||||
|
import { SharePointService } from "../../utils/SharePointService";
|
||||||
|
|
||||||
export interface IBirthdaysPerMonthWebPartProps {
|
export interface IBirthdaysPerMonthWebPartProps {
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BirthdaysPerMonthWebPart extends BaseClientSideWebPart<IBirthdaysPerMonthWebPartProps> {
|
export default class BirthdaysPerMonthWebPart extends BaseClientSideWebPart<IBirthdaysPerMonthWebPartProps> {
|
||||||
private _isDarkTheme: boolean = false;
|
private _spfi: SPFI;
|
||||||
private _environmentMessage: string = "";
|
|
||||||
|
|
||||||
public render(): void {
|
public async render(): Promise<void> {
|
||||||
|
const sharePointService = new SharePointService(this._spfi);
|
||||||
|
const birthdays: Array<BirthdaysInMonth> =
|
||||||
|
await sharePointService.GetBirthdays();
|
||||||
|
const elementProps: IBirthdaysPerMonthProps = {
|
||||||
|
data: birthdays,
|
||||||
|
};
|
||||||
const element: React.ReactElement<IBirthdaysPerMonthProps> =
|
const element: React.ReactElement<IBirthdaysPerMonthProps> =
|
||||||
React.createElement(BirthdaysPerMonth, {
|
React.createElement(BirthdaysPerMonth, elementProps);
|
||||||
description: this.properties.description,
|
|
||||||
isDarkTheme: this._isDarkTheme,
|
|
||||||
environmentMessage: this._environmentMessage,
|
|
||||||
hasTeamsContext: !!this.context.sdks.microsoftTeams,
|
|
||||||
userDisplayName: this.context.pageContext.user.displayName,
|
|
||||||
});
|
|
||||||
|
|
||||||
ReactDom.render(element, this.domElement);
|
ReactDom.render(element, this.domElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onInit(): Promise<void> {
|
protected onInit(): Promise<void> {
|
||||||
|
this._spfi = spfi().using(SPFx(this.context));
|
||||||
return super.onInit();
|
return super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,87 +1,17 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import styles from "./BirthdaysPerMonth.module.scss";
|
import { BirthdaysInMonth } from "../../../models/BirthdaysInMonth";
|
||||||
import { escape } from "@microsoft/sp-lodash-subset";
|
import { MonthSection } from "./MonthSection";
|
||||||
|
|
||||||
interface IBirthdaysPerMonthProps {}
|
interface IBirthdaysPerMonthProps {
|
||||||
|
data: Array<BirthdaysInMonth>;
|
||||||
|
}
|
||||||
|
|
||||||
const BirthdaysPerMonth = () => {
|
const BirthdaysPerMonth = (props: IBirthdaysPerMonthProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<section className={`tw-text-red-700`}>
|
<section>
|
||||||
<div className={styles.welcome}>
|
{props.data.map((month, index) => (
|
||||||
<img
|
<MonthSection key={month.title} data={month} index={index} />
|
||||||
alt=""
|
))}
|
||||||
src={require("../assets/welcome-light.png")}
|
|
||||||
className={styles.welcomeImage}
|
|
||||||
/>
|
|
||||||
<h2>Well done, Ari!</h2>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>Welcome to SharePoint Framework!</h3>
|
|
||||||
<p>
|
|
||||||
The SharePoint Framework (SPFx) is a extensibility model for Microsoft
|
|
||||||
Viva, Microsoft Teams and SharePoint. It's the easiest way to
|
|
||||||
extend Microsoft 365 with automatic Single Sign On, automatic hosting
|
|
||||||
and industry standard tooling.
|
|
||||||
</p>
|
|
||||||
<h4>Learn more about SPFx development:</h4>
|
|
||||||
<ul className={styles.links}>
|
|
||||||
<li>
|
|
||||||
<a href="https://aka.ms/spfx" target="_blank" rel="noreferrer">
|
|
||||||
SharePoint Framework Overview
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://aka.ms/spfx-yeoman-graph"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Use Microsoft Graph in your solution
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://aka.ms/spfx-yeoman-teams"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Build for Microsoft Teams using SharePoint Framework
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://aka.ms/spfx-yeoman-viva"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Build for Microsoft Viva Connections using SharePoint Framework
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://aka.ms/spfx-yeoman-store"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Publish SharePoint Framework applications to the marketplace
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://aka.ms/spfx-yeoman-api"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
SharePoint Framework API reference
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://aka.ms/m365pnp" target="_blank" rel="noreferrer">
|
|
||||||
Microsoft 365 Developer Community
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { addYears, format } from "date-fns/esm";
|
||||||
|
import formatDistance from "date-fns/formatDistance";
|
||||||
|
import set from "date-fns/set";
|
||||||
|
import { Icon, IconButton } from "office-ui-fabric-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { BirthdaysInMonth } from "../../../models/BirthdaysInMonth";
|
||||||
|
|
||||||
|
interface IMonthSectionProps {
|
||||||
|
data: BirthdaysInMonth;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MonthSection = (props: IMonthSectionProps): JSX.Element => {
|
||||||
|
const [isExpand, toggleExpand] = React.useReducer(
|
||||||
|
(previous) => !previous,
|
||||||
|
props.index === 0 || props.index === 1
|
||||||
|
);
|
||||||
|
|
||||||
|
const generateYearLabel = (): string => {
|
||||||
|
if (props.data.title !== "January" || props.index === 0) return "";
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
today.setFullYear(today.getFullYear() + 1);
|
||||||
|
return " " + today.getFullYear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDate = (date: number, month: number): Date => {
|
||||||
|
let birthdateDate = set(new Date(), { month, date });
|
||||||
|
if (birthdateDate.getMonth() < new Date().getMonth()) {
|
||||||
|
birthdateDate = addYears(birthdateDate, 1);
|
||||||
|
}
|
||||||
|
return birthdateDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateDateLabel = (date: number, month: number): string => {
|
||||||
|
const birthdateDate = getDate(date, month);
|
||||||
|
return format(birthdateDate, "E, d MMM");
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateDistanceLabel = (date: number, month: number): string => {
|
||||||
|
const birthdateDate = getDate(date, month);
|
||||||
|
return formatDistance(birthdateDate, new Date(), { addSuffix: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onClick={toggleExpand}>
|
||||||
|
<div className="hover:tw-cursor-pointer tw-p-3 tw-bg-[#f3f2f1] tw-font-semibold tw-text-lg tw-flex tw-gap-2">
|
||||||
|
<Icon
|
||||||
|
className={`${isExpand ? "tw-rotate-90" : ""} tw-transition-all`}
|
||||||
|
iconName="ChevronRightMed"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
{props.data.title}
|
||||||
|
{generateYearLabel()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isExpand && (
|
||||||
|
<div className="tw-flex tw-gap-8 tw-p-3 tw-pl-[2.35rem] tw-border tw-border-solid tw-border-[#f3f2f1]">
|
||||||
|
{props.data.users.length === 0 && <div>No birthdays this month.</div>}
|
||||||
|
{props.data.users.map((user) => (
|
||||||
|
<div className="tw-flex tw-gap-4" key={user.id}>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
className="tw-rounded-full tw-w-20"
|
||||||
|
src={`/_layouts/15/userphoto.aspx?UserName=${user.email}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="tw-flex tw-flex-col tw-justify-center">
|
||||||
|
<div className="tw-text-lg tw-font-semibold tw-flex tw-gap-1 tw-items-center">
|
||||||
|
<div>{user.name}</div>
|
||||||
|
<IconButton
|
||||||
|
iconProps={{ iconName: "Mail" }}
|
||||||
|
title="Mail"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
window.open(`mailto:${user.email}`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>{generateDateLabel(user.date, user.monthIndex)}</div>
|
||||||
|
<div>{generateDistanceLabel(user.date, user.monthIndex)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { MonthSection };
|
Loading…
Reference in New Issue