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()
|
||||
// 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>".
|
||||
'@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()
|
||||
// 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
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-birthdays-per-month-client-side-solution",
|
||||
"name": "React Birthdays Per Month",
|
||||
"id": "e4cd97a3-4515-42cd-8a12-f765eaf60caa",
|
||||
"version": "1.0.0.0",
|
||||
"version": "1.1.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
|
@ -35,6 +35,6 @@
|
|||
]
|
||||
},
|
||||
"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-property-pane": "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",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
|
|
|
@ -11,12 +11,14 @@ specifiers:
|
|||
'@microsoft/sp-office-ui-fabric-core': 1.15.2
|
||||
'@microsoft/sp-property-pane': 1.15.2
|
||||
'@microsoft/sp-webpart-base': 1.15.2
|
||||
'@pnp/sp': ^3.8.0
|
||||
'@rushstack/eslint-config': 2.5.1
|
||||
'@types/react': 16.9.51
|
||||
'@types/react-dom': 16.9.8
|
||||
'@types/webpack-env': ~1.15.2
|
||||
ajv: ^6.12.5
|
||||
autoprefixer: ^10.4.13
|
||||
date-fns: ^2.29.3
|
||||
eslint-plugin-react-hooks: 4.3.0
|
||||
gulp: 4.0.2
|
||||
gulp-postcss: ^9.0.1
|
||||
|
@ -36,6 +38,8 @@ dependencies:
|
|||
'@microsoft/sp-office-ui-fabric-core': 1.15.2
|
||||
'@microsoft/sp-property-pane': 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
|
||||
react: 16.13.1
|
||||
react-dom: 16.13.1_react@16.13.1
|
||||
|
@ -76,7 +80,7 @@ packages:
|
|||
resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/@azure/core-asynciterator-polyfill/1.0.2:
|
||||
|
@ -89,7 +93,7 @@ packages:
|
|||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@azure/abort-controller': 1.1.0
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/@azure/core-http/1.2.6:
|
||||
|
@ -107,7 +111,7 @@ packages:
|
|||
node-fetch: 2.6.7
|
||||
process: 0.11.10
|
||||
tough-cookie: 4.1.2
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
tunnel: 0.0.6
|
||||
uuid: 8.3.2
|
||||
xml2js: 0.4.23
|
||||
|
@ -123,7 +127,7 @@ packages:
|
|||
'@azure/core-http': 1.2.6
|
||||
'@azure/core-tracing': 1.0.0-preview.11
|
||||
events: 3.3.0
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
@ -132,7 +136,7 @@ packages:
|
|||
resolution: {integrity: sha512-H6Tg9eBm0brHqLy0OSAGzxIh1t4UL8eZVrSUMJ60Ra9cwq2pOskFqVpz2pYoHDsBY1jZ4V/P8LRGb5D5pmC6rg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/@azure/core-tracing/1.0.0-preview.11:
|
||||
|
@ -141,7 +145,7 @@ packages:
|
|||
dependencies:
|
||||
'@opencensus/web-types': 0.0.7
|
||||
'@opentelemetry/api': 1.0.0-rc.0
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/@azure/core-tracing/1.0.0-preview.7:
|
||||
|
@ -158,7 +162,7 @@ packages:
|
|||
dependencies:
|
||||
'@opencensus/web-types': 0.0.7
|
||||
'@opentelemetry/api': 0.10.2
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/@azure/identity/1.0.3:
|
||||
|
@ -182,7 +186,7 @@ packages:
|
|||
resolution: {integrity: sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/@azure/msal-browser/2.22.0:
|
||||
|
@ -208,7 +212,7 @@ packages:
|
|||
'@azure/logger': 1.0.3
|
||||
'@opentelemetry/api': 0.10.2
|
||||
events: 3.3.0
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
@ -225,7 +229,7 @@ packages:
|
|||
'@azure/logger': 1.0.3
|
||||
'@opentelemetry/api': 0.10.2
|
||||
events: 3.3.0
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
@ -1126,7 +1130,7 @@ packages:
|
|||
dependencies:
|
||||
'@azure/msal-browser': 2.22.0
|
||||
'@babel/runtime': 7.20.0
|
||||
tslib: 2.3.1
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@microsoft/office-ui-fabric-react-bundle/1.15.2_7ombvvupg4tnmt4iqt5m47i6cu:
|
||||
|
@ -1790,6 +1794,31 @@ packages:
|
|||
webpack-dev-server: 3.11.2_jtacdaqalodjhudd6qhs6dld5q
|
||||
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:
|
||||
resolution: {integrity: sha512-vxkRrkneBPVmP23kyjnYwVOtipwlSl6UfL+h+Xa3TrABJTz5rYBXemlTsU5BzST8U4pD7YDkTb3SQu+MMuIDKA==}
|
||||
engines: {node: '>=10.16'}
|
||||
|
@ -5490,6 +5519,11 @@ packages:
|
|||
whatwg-url: 7.1.0
|
||||
dev: true
|
||||
|
||||
/date-fns/2.29.3:
|
||||
resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
|
||||
engines: {node: '>=0.11'}
|
||||
dev: false
|
||||
|
||||
/dateformat/1.0.12:
|
||||
resolution: {integrity: sha512-5sFRfAAmbHdIts+eKjR9kYJoF0ViCMVX9yqLu5A7S/v+nd077KgCITOMiirmyCBiZpKLDXbBOkYm6tu7rX/TKg==}
|
||||
hasBin: true
|
||||
|
@ -14185,6 +14219,10 @@ packages:
|
|||
|
||||
/tslib/2.3.1:
|
||||
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:
|
||||
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": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced
|
||||
"group": { "default": "Advanced" },
|
||||
"title": { "default": "BirthdaysPerMonth" },
|
||||
"description": { "default": "BirthdaysPerMonth description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"title": { "default": "Birthdays Per Month" },
|
||||
"description": { "default": "" },
|
||||
"officeFabricIconFontName": "BirthdayCake",
|
||||
"properties": {
|
||||
"description": "BirthdaysPerMonth"
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
PropertyPaneTextField,
|
||||
} from "@microsoft/sp-property-pane";
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { spfi, SPFx, SPFI } from "@pnp/sp";
|
||||
|
||||
import * as strings from "BirthdaysPerMonthWebPartStrings";
|
||||
import "../../../assets/dist/tailwind.css";
|
||||
|
@ -13,29 +14,31 @@ import {
|
|||
BirthdaysPerMonth,
|
||||
IBirthdaysPerMonthProps,
|
||||
} from "./components/BirthdaysPerMonth";
|
||||
import { BirthdaysInMonth } from "../../models/BirthdaysInMonth";
|
||||
import { SharePointService } from "../../utils/SharePointService";
|
||||
|
||||
export interface IBirthdaysPerMonthWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class BirthdaysPerMonthWebPart extends BaseClientSideWebPart<IBirthdaysPerMonthWebPartProps> {
|
||||
private _isDarkTheme: boolean = false;
|
||||
private _environmentMessage: string = "";
|
||||
private _spfi: SPFI;
|
||||
|
||||
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> =
|
||||
React.createElement(BirthdaysPerMonth, {
|
||||
description: this.properties.description,
|
||||
isDarkTheme: this._isDarkTheme,
|
||||
environmentMessage: this._environmentMessage,
|
||||
hasTeamsContext: !!this.context.sdks.microsoftTeams,
|
||||
userDisplayName: this.context.pageContext.user.displayName,
|
||||
});
|
||||
React.createElement(BirthdaysPerMonth, elementProps);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onInit(): Promise<void> {
|
||||
this._spfi = spfi().using(SPFx(this.context));
|
||||
return super.onInit();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,87 +1,17 @@
|
|||
import * as React from "react";
|
||||
import styles from "./BirthdaysPerMonth.module.scss";
|
||||
import { escape } from "@microsoft/sp-lodash-subset";
|
||||
import { BirthdaysInMonth } from "../../../models/BirthdaysInMonth";
|
||||
import { MonthSection } from "./MonthSection";
|
||||
|
||||
interface IBirthdaysPerMonthProps {}
|
||||
interface IBirthdaysPerMonthProps {
|
||||
data: Array<BirthdaysInMonth>;
|
||||
}
|
||||
|
||||
const BirthdaysPerMonth = () => {
|
||||
const BirthdaysPerMonth = (props: IBirthdaysPerMonthProps): JSX.Element => {
|
||||
return (
|
||||
<section className={`tw-text-red-700`}>
|
||||
<div className={styles.welcome}>
|
||||
<img
|
||||
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>
|
||||
{props.data.map((month, index) => (
|
||||
<MonthSection key={month.title} data={month} index={index} />
|
||||
))}
|
||||
</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