add show recurring events property
This commit is contained in:
parent
f2f0679c43
commit
13be45e4cd
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "react-graph-calendar-client-side-solution",
|
||||
"id": "42fe0a0f-c4d9-4b05-806c-3857decb3d71",
|
||||
"version": "1.0.0.0",
|
||||
"version": "1.0.1.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
|
|
|
@ -7093,7 +7093,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
|
||||
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es5-ext": "^0.10.50",
|
||||
"type": "^1.0.1"
|
||||
|
@ -7697,7 +7696,6 @@
|
|||
"version": "0.10.53",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
|
||||
"integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es6-iterator": "~2.0.3",
|
||||
"es6-symbol": "~3.1.3",
|
||||
|
@ -7713,7 +7711,6 @@
|
|||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"d": "1",
|
||||
"es5-ext": "^0.10.35",
|
||||
|
@ -7777,7 +7774,6 @@
|
|||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
|
||||
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"d": "^1.0.1",
|
||||
"ext": "^1.1.2"
|
||||
|
@ -8171,7 +8167,6 @@
|
|||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
|
||||
"integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type": "^2.0.0"
|
||||
},
|
||||
|
@ -8179,8 +8174,7 @@
|
|||
"type": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
|
||||
"integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==",
|
||||
"dev": true
|
||||
"integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -13579,6 +13573,14 @@
|
|||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
||||
},
|
||||
"moment-range": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/moment-range/-/moment-range-4.0.2.tgz",
|
||||
"integrity": "sha512-n8sceWwSTjmz++nFHzeNEUsYtDqjgXgcOBzsHi+BoXQU2FW+eU92LUaK8gqOiSu5PG57Q9sYj1Fz4LRDj4FtKA==",
|
||||
"requires": {
|
||||
"es6-symbol": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"moment-timezone": {
|
||||
"version": "0.5.27",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz",
|
||||
|
@ -13711,8 +13713,7 @@
|
|||
"next-tick": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
|
||||
"dev": true
|
||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||
},
|
||||
"nice-try": {
|
||||
"version": "1.0.5",
|
||||
|
@ -18013,8 +18014,7 @@
|
|||
"type": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
||||
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.3.2",
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"moment": "^2.24.0",
|
||||
"moment-range": "^4.0.2",
|
||||
"moment-timezone": "^0.5.27",
|
||||
"office-ui-fabric-react": "6.189.2",
|
||||
"react": "16.8.5",
|
||||
|
|
|
@ -3,7 +3,6 @@ import * as ReactDom from 'react-dom';
|
|||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
|
@ -12,10 +11,11 @@ import GraphCalendar from './components/GraphCalendar';
|
|||
import { IGraphCalendarProps } from './components/IGraphCalendarProps';
|
||||
import * as microsoftTeams from '@microsoft/teams-js';
|
||||
import { initializeIcons } from 'office-ui-fabric-react';
|
||||
import { PropertyPaneSlider } from '@microsoft/sp-property-pane';
|
||||
import { PropertyPaneSlider, PropertyPaneCheckbox, IPropertyPaneConfiguration } from '@microsoft/sp-property-pane';
|
||||
|
||||
export interface IGraphCalendarWebPartProps {
|
||||
limit: number;
|
||||
showRecurrence: boolean;
|
||||
}
|
||||
|
||||
export default class GraphCalendarWebPart extends BaseClientSideWebPart<IGraphCalendarWebPartProps> {
|
||||
|
@ -26,6 +26,7 @@ export default class GraphCalendarWebPart extends BaseClientSideWebPart<IGraphCa
|
|||
GraphCalendar,
|
||||
{
|
||||
limit: this.properties.limit,
|
||||
showRecurrence: this.properties.showRecurrence,
|
||||
context: this.context,
|
||||
teamsContext: this._teamsContext
|
||||
}
|
||||
|
@ -43,6 +44,10 @@ export default class GraphCalendarWebPart extends BaseClientSideWebPart<IGraphCa
|
|||
this.properties.limit = 100;
|
||||
}
|
||||
|
||||
if (this.properties.showRecurrence === undefined) {
|
||||
this.properties.showRecurrence = true;
|
||||
}
|
||||
|
||||
// Sets the Teams context if in Teams
|
||||
if (this.context.sdks.microsoftTeams) {
|
||||
this._teamsContext = this.context.sdks.microsoftTeams.context;
|
||||
|
@ -82,6 +87,10 @@ export default class GraphCalendarWebPart extends BaseClientSideWebPart<IGraphCa
|
|||
label: "Events to load per active view",
|
||||
max: 500,
|
||||
min: 50
|
||||
}),
|
||||
PropertyPaneCheckbox('showRecurrence', {
|
||||
text: "Show recurring events",
|
||||
checked: true
|
||||
})
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import { EventInput } from '@fullcalendar/core';
|
|||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import * as moment from 'moment-timezone';
|
||||
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
|
||||
import { extendMoment } from 'moment-range';
|
||||
|
||||
const { range } = extendMoment(moment);
|
||||
|
||||
interface IGraphCalendarState {
|
||||
events: EventInput[];
|
||||
|
@ -192,46 +195,19 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
|||
}
|
||||
|
||||
/**
|
||||
* Loads the Events based on the current state of the Calendar
|
||||
* @param startDate The first visible date on the calendar
|
||||
* @param endDate The last visible date on the calendar
|
||||
* Convert data to Array<EventInput>
|
||||
* @param data Events from API
|
||||
*/
|
||||
private _loadEvents(startDate: Date, endDate: Date): void {
|
||||
|
||||
// If a Group was found or running in the context of a Personal tab, execute the query. If not, do nothing.
|
||||
if(this.state.groupId || this.state.tabType == TabType.PersonalTab) {
|
||||
|
||||
this.props.context.msGraphClientFactory
|
||||
.getClient()
|
||||
.then((client: MSGraphClient): void => {
|
||||
|
||||
let apiUrl: string = `/groups/${this.state.groupId}/events`;
|
||||
if(this._isPersonalTab()) {
|
||||
apiUrl = '/me/events';
|
||||
}
|
||||
|
||||
client
|
||||
.api(apiUrl)
|
||||
.version("v1.0")
|
||||
.select('subject,start,end,location,bodyPreview,isAllDay')
|
||||
.filter(`start/dateTime ge '${startDate.toISOString()}' and end/dateTime le '${endDate.toISOString()}'`)
|
||||
.top(this.props.limit)
|
||||
.get((err, res) => {
|
||||
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var events: Array<EventInput> = new Array<EventInput>();
|
||||
|
||||
res.value.map((item: any) => {
|
||||
private _transformEvents(data: any): Array<EventInput> {
|
||||
let events: Array<EventInput> = new Array<EventInput>();
|
||||
data.value.map((item: any) => {
|
||||
// Build a Timezone enabled Date
|
||||
let currentStartDate = moment.tz(item.start.dateTime, item.start.timeZone);
|
||||
let currentEndDate = moment.tz(item.end.dateTime, item.end.timeZone);
|
||||
|
||||
// Adding all retrieved events to the result array
|
||||
events.push({
|
||||
id: item.id,
|
||||
title: item.subject,
|
||||
|
||||
// If the event is an All Day event, add 1 day without Timezone to the start date
|
||||
|
@ -241,13 +217,112 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
|||
end: !item.isAllDay ? currentEndDate.clone().tz(Intl.DateTimeFormat().resolvedOptions().timeZone).format() : moment(currentEndDate).add(1, 'd').toISOString(),
|
||||
allDay: item.isAllDay,
|
||||
location: item.location.displayName,
|
||||
body: item.bodyPreview
|
||||
body: item.bodyPreview,
|
||||
type: item.type
|
||||
});
|
||||
});
|
||||
return events;
|
||||
}
|
||||
|
||||
// Sets the state with the retrieved events and current active calendar dates
|
||||
/**
|
||||
* Check if the recurring events need to be shown on the current state of the Calendar
|
||||
* If the range of the recurring event overlaps the range of the Calendar, then the event needs to be shown.
|
||||
* @param data All the recurrent (base) events ever made
|
||||
* @param startDate The first visible date on the calendar
|
||||
* @param endDate The last visible date on the calendar
|
||||
*/
|
||||
private _filterRecEvents(data: any, startDate: Date, endDate: Date): Array<EventInput> {
|
||||
let events: Array<EventInput> = new Array<EventInput>();
|
||||
//Range of the Calendar
|
||||
var r1 = range(startDate, endDate);
|
||||
|
||||
data.value.map((item: any) => {
|
||||
// Build a Timezone enabled Date
|
||||
let currentStartDate = moment.tz(item.start.dateTime, item.start.timeZone);
|
||||
let currentEndDate = moment.tz(item.end.dateTime, item.end.timeZone);
|
||||
|
||||
var d1 = item.recurrence.range.startDate;
|
||||
var d2 = item.recurrence.range.endDate;
|
||||
var recStartDate = moment(d1).toDate();
|
||||
var recEndDate = moment(d2).toDate();
|
||||
|
||||
//Range of the recurring event item
|
||||
var r2 = range(recStartDate, recEndDate);
|
||||
|
||||
|
||||
//Check if both ranges overlap
|
||||
if (!!r1.overlaps(r2)) {
|
||||
events.push({
|
||||
id: item.id,
|
||||
title: item.subject,
|
||||
// If the event is an All Day event, add 1 day without Timezone to the start date
|
||||
start: !item.isAllDay ? currentStartDate.clone().tz(Intl.DateTimeFormat().resolvedOptions().timeZone).format() : moment(currentStartDate).add(1, 'd').toISOString(),
|
||||
// If the event is an All Day event, add 1 day without Timezone to the end date
|
||||
end: !item.isAllDay ? currentEndDate.clone().tz(Intl.DateTimeFormat().resolvedOptions().timeZone).format() : moment(currentEndDate).add(1, 'd').toISOString(),
|
||||
allDay: item.isAllDay,
|
||||
location: item.location.displayName,
|
||||
body: item.bodyPreview,
|
||||
type: item.type
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the Events based on the current state of the Calendar
|
||||
* @param startDate The first visible date on the calendar
|
||||
* @param endDate The last visible date on the calendar
|
||||
*/
|
||||
private _loadEvents(startDate: Date, endDate: Date): void {
|
||||
|
||||
// If a Group was found or running in the context of a Personal tab, execute the query. If not, do nothing.
|
||||
if (this.state.groupId || this.state.tabType == TabType.PersonalTab) {
|
||||
var events: Array<EventInput> = new Array<EventInput>();
|
||||
|
||||
this.props.context.msGraphClientFactory
|
||||
.getClient()
|
||||
.then((client: MSGraphClient): void => {
|
||||
let apiUrl: string = `/groups/${this.state.groupId}/events`;
|
||||
if (this._isPersonalTab()) {
|
||||
apiUrl = '/me/events';
|
||||
}
|
||||
|
||||
client
|
||||
.api(apiUrl)
|
||||
.version("v1.0")
|
||||
.select('subject,start,end,location,bodyPreview,isAllDay,type')
|
||||
.filter(`start/dateTime ge '${startDate.toISOString()}' and end/dateTime le '${endDate.toISOString()}' and type eq 'singleInstance'`)
|
||||
.top(this.props.limit)
|
||||
.get((err, res) => {
|
||||
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//Transform API data to Array<EventInput>
|
||||
events = this._transformEvents(res);
|
||||
|
||||
if (this.props.showRecurrence) {
|
||||
//Get recurring events and merge with the other (standard) events
|
||||
this._getRecurringMaster(startDate, endDate).then((recData: Array<EventInput>) => {
|
||||
this._getRecurrentEvents(recData, startDate, endDate).then((recEvents: Array<EventInput>) => {
|
||||
this.setState({
|
||||
events: [...recEvents, ...events].slice(0, this.props.limit),
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
//Show only (standard) events
|
||||
this.setState({
|
||||
events: events,
|
||||
});
|
||||
}
|
||||
|
||||
// Sets the state with current active calendar dates
|
||||
this.setState({
|
||||
currentActiveStartDate: startDate,
|
||||
currentActiveEndDate: endDate,
|
||||
currentSelectedEvent: null
|
||||
|
@ -256,4 +331,76 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all recurrent events based on the current state of the Calendar
|
||||
* @param events All the recurrent base events
|
||||
* @param startDate The first visible date on the calendar
|
||||
* @param endDate The last visible date on the calendar
|
||||
*/
|
||||
private _getRecurrentEvents(events: Array<EventInput>, startDate: Date, endDate: Date): Promise<Array<EventInput>> {
|
||||
return new Promise<Array<EventInput>>((resolve, reject) => {
|
||||
this.props.context.msGraphClientFactory
|
||||
.getClient()
|
||||
.then((client: MSGraphClient): void => {
|
||||
var recEvents: Array<EventInput> = new Array<EventInput>();
|
||||
var count = 0;
|
||||
events.map((item: any) => {
|
||||
let apiUrl: string = `/groups/${this.state.groupId}/events/${item.id}/instances?startDateTime=${startDate.toISOString()}&endDateTime=${endDate.toISOString()}`;
|
||||
if(this._isPersonalTab()) {
|
||||
apiUrl = `/me/events/${item.id}/instances?startDateTime=${startDate.toISOString()}&endDateTime=${endDate.toISOString()}`;
|
||||
}
|
||||
client
|
||||
.api(apiUrl)
|
||||
.version("v1.0")
|
||||
.select('subject,start,end,location,bodyPreview,isAllDay,type')
|
||||
.get((err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
recEvents = recEvents.concat(this._transformEvents(res));
|
||||
count += 1;
|
||||
if (count == events.length) {
|
||||
resolve(recEvents);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all recurrent (base) events ever created
|
||||
* Filter the base events based on the current state of the Calendar
|
||||
* @param startDate The first visible date on the calendar
|
||||
* @param endDate The last visible date on the calendar
|
||||
*/
|
||||
private _getRecurringMaster(startDate: Date, endDate: Date): Promise<Array<EventInput>> {
|
||||
return new Promise<Array<EventInput>>((resolve, reject) => {
|
||||
this.props.context.msGraphClientFactory
|
||||
.getClient()
|
||||
.then((client: MSGraphClient): void => {
|
||||
let apiUrl: string = `/groups/${this.state.groupId}/events`;
|
||||
if(this._isPersonalTab()) {
|
||||
apiUrl = '/me/events';
|
||||
}
|
||||
client
|
||||
.api(apiUrl)
|
||||
.version("v1.0")
|
||||
.select('subject,start,end,location,bodyPreview,isAllDay,type,recurrence')
|
||||
.filter(`type eq 'seriesMaster'`) //recurrening event is type 'seriesMaster'
|
||||
.get((err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
var recEvents: Array<EventInput> = new Array<EventInput>();
|
||||
recEvents = this._filterRecEvents(res, startDate, endDate);
|
||||
resolve(recEvents);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as microsoftTeams from '@microsoft/teams-js';
|
|||
|
||||
export interface IGraphCalendarProps {
|
||||
limit: number;
|
||||
showRecurrence: boolean;
|
||||
context: WebPartContext;
|
||||
teamsContext: microsoftTeams.Context;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue