add show recurring events property
This commit is contained in:
parent
f2f0679c43
commit
13be45e4cd
|
@ -3,7 +3,7 @@
|
||||||
"solution": {
|
"solution": {
|
||||||
"name": "react-graph-calendar-client-side-solution",
|
"name": "react-graph-calendar-client-side-solution",
|
||||||
"id": "42fe0a0f-c4d9-4b05-806c-3857decb3d71",
|
"id": "42fe0a0f-c4d9-4b05-806c-3857decb3d71",
|
||||||
"version": "1.0.0.0",
|
"version": "1.0.1.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"skipFeatureDeployment": true,
|
"skipFeatureDeployment": true,
|
||||||
"isDomainIsolated": false,
|
"isDomainIsolated": false,
|
||||||
|
|
|
@ -7093,7 +7093,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
|
||||||
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
|
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"es5-ext": "^0.10.50",
|
"es5-ext": "^0.10.50",
|
||||||
"type": "^1.0.1"
|
"type": "^1.0.1"
|
||||||
|
@ -7697,7 +7696,6 @@
|
||||||
"version": "0.10.53",
|
"version": "0.10.53",
|
||||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
|
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
|
||||||
"integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
|
"integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"es6-iterator": "~2.0.3",
|
"es6-iterator": "~2.0.3",
|
||||||
"es6-symbol": "~3.1.3",
|
"es6-symbol": "~3.1.3",
|
||||||
|
@ -7713,7 +7711,6 @@
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||||
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
|
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"d": "1",
|
"d": "1",
|
||||||
"es5-ext": "^0.10.35",
|
"es5-ext": "^0.10.35",
|
||||||
|
@ -7777,7 +7774,6 @@
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
|
||||||
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
|
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"d": "^1.0.1",
|
"d": "^1.0.1",
|
||||||
"ext": "^1.1.2"
|
"ext": "^1.1.2"
|
||||||
|
@ -8171,7 +8167,6 @@
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
|
||||||
"integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
|
"integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"type": "^2.0.0"
|
"type": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
@ -8179,8 +8174,7 @@
|
||||||
"type": {
|
"type": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
|
||||||
"integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==",
|
"integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13579,6 +13573,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
||||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
"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": {
|
"moment-timezone": {
|
||||||
"version": "0.5.27",
|
"version": "0.5.27",
|
||||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz",
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz",
|
||||||
|
@ -13711,8 +13713,7 @@
|
||||||
"next-tick": {
|
"next-tick": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
|
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"nice-try": {
|
"nice-try": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
|
@ -18013,8 +18014,7 @@
|
||||||
"type": {
|
"type": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
||||||
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
|
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"type-check": {
|
"type-check": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"@types/react-dom": "16.8.3",
|
"@types/react-dom": "16.8.3",
|
||||||
"@types/webpack-env": "1.13.1",
|
"@types/webpack-env": "1.13.1",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
|
"moment-range": "^4.0.2",
|
||||||
"moment-timezone": "^0.5.27",
|
"moment-timezone": "^0.5.27",
|
||||||
"office-ui-fabric-react": "6.189.2",
|
"office-ui-fabric-react": "6.189.2",
|
||||||
"react": "16.8.5",
|
"react": "16.8.5",
|
||||||
|
|
|
@ -3,7 +3,6 @@ import * as ReactDom from 'react-dom';
|
||||||
import { Version } from '@microsoft/sp-core-library';
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
import {
|
import {
|
||||||
BaseClientSideWebPart,
|
BaseClientSideWebPart,
|
||||||
IPropertyPaneConfiguration,
|
|
||||||
PropertyPaneTextField
|
PropertyPaneTextField
|
||||||
} from '@microsoft/sp-webpart-base';
|
} from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
@ -12,10 +11,11 @@ import GraphCalendar from './components/GraphCalendar';
|
||||||
import { IGraphCalendarProps } from './components/IGraphCalendarProps';
|
import { IGraphCalendarProps } from './components/IGraphCalendarProps';
|
||||||
import * as microsoftTeams from '@microsoft/teams-js';
|
import * as microsoftTeams from '@microsoft/teams-js';
|
||||||
import { initializeIcons } from 'office-ui-fabric-react';
|
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 {
|
export interface IGraphCalendarWebPartProps {
|
||||||
limit: number;
|
limit: number;
|
||||||
|
showRecurrence: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class GraphCalendarWebPart extends BaseClientSideWebPart<IGraphCalendarWebPartProps> {
|
export default class GraphCalendarWebPart extends BaseClientSideWebPart<IGraphCalendarWebPartProps> {
|
||||||
|
@ -26,6 +26,7 @@ export default class GraphCalendarWebPart extends BaseClientSideWebPart<IGraphCa
|
||||||
GraphCalendar,
|
GraphCalendar,
|
||||||
{
|
{
|
||||||
limit: this.properties.limit,
|
limit: this.properties.limit,
|
||||||
|
showRecurrence: this.properties.showRecurrence,
|
||||||
context: this.context,
|
context: this.context,
|
||||||
teamsContext: this._teamsContext
|
teamsContext: this._teamsContext
|
||||||
}
|
}
|
||||||
|
@ -43,6 +44,10 @@ export default class GraphCalendarWebPart extends BaseClientSideWebPart<IGraphCa
|
||||||
this.properties.limit = 100;
|
this.properties.limit = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.properties.showRecurrence === undefined) {
|
||||||
|
this.properties.showRecurrence = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Sets the Teams context if in Teams
|
// Sets the Teams context if in Teams
|
||||||
if (this.context.sdks.microsoftTeams) {
|
if (this.context.sdks.microsoftTeams) {
|
||||||
this._teamsContext = this.context.sdks.microsoftTeams.context;
|
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",
|
label: "Events to load per active view",
|
||||||
max: 500,
|
max: 500,
|
||||||
min: 50
|
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 dayGridPlugin from '@fullcalendar/daygrid';
|
||||||
import * as moment from 'moment-timezone';
|
import * as moment from 'moment-timezone';
|
||||||
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
|
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
|
||||||
|
import { extendMoment } from 'moment-range';
|
||||||
|
|
||||||
|
const { range } = extendMoment(moment);
|
||||||
|
|
||||||
interface IGraphCalendarState {
|
interface IGraphCalendarState {
|
||||||
events: EventInput[];
|
events: EventInput[];
|
||||||
|
@ -36,10 +39,10 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// If this is running in Teams, embed the specific Teams styling
|
// If this is running in Teams, embed the specific Teams styling
|
||||||
if(this._isRunningInTeams()) {
|
if (this._isRunningInTeams()) {
|
||||||
import("./GraphCalendar.Teams.module.scss");
|
import("./GraphCalendar.Teams.module.scss");
|
||||||
|
|
||||||
if(this.props.teamsContext.theme == "dark") {
|
if (this.props.teamsContext.theme == "dark") {
|
||||||
import("./GraphCalendar.Teams.Dark.module.scss");
|
import("./GraphCalendar.Teams.Dark.module.scss");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,11 +76,11 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
||||||
*/
|
*/
|
||||||
public render(): React.ReactElement<IGraphCalendarProps> {
|
public render(): React.ReactElement<IGraphCalendarProps> {
|
||||||
return (
|
return (
|
||||||
<div className={ styles.graphCalendar }>
|
<div className={styles.graphCalendar}>
|
||||||
<FullCalendar
|
<FullCalendar
|
||||||
ref={this.calendar}
|
ref={this.calendar}
|
||||||
defaultView="dayGridMonth"
|
defaultView="dayGridMonth"
|
||||||
plugins={[ dayGridPlugin ]}
|
plugins={[dayGridPlugin]}
|
||||||
windowResize={this._handleResize.bind(this)}
|
windowResize={this._handleResize.bind(this)}
|
||||||
datesRender={this._datesRender.bind(this)}
|
datesRender={this._datesRender.bind(this)}
|
||||||
eventClick={this._openEventPanel.bind(this)}
|
eventClick={this._openEventPanel.bind(this)}
|
||||||
|
@ -86,7 +89,7 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
||||||
{this.state.currentSelectedEvent &&
|
{this.state.currentSelectedEvent &&
|
||||||
<Panel
|
<Panel
|
||||||
isOpen={this.state.isEventDetailsOpen}
|
isOpen={this.state.isEventDetailsOpen}
|
||||||
type={ PanelType.smallFixedFar }
|
type={PanelType.smallFixedFar}
|
||||||
headerText={this.state.currentSelectedEvent ? this.state.currentSelectedEvent.title : ""}
|
headerText={this.state.currentSelectedEvent ? this.state.currentSelectedEvent.title : ""}
|
||||||
onDismiss={this._closeEventPanel.bind(this)}
|
onDismiss={this._closeEventPanel.bind(this)}
|
||||||
isLightDismiss={true}
|
isLightDismiss={true}
|
||||||
|
@ -118,7 +121,7 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
||||||
* Mainly used for Teams validation so it renders "full-screen" in Teams
|
* Mainly used for Teams validation so it renders "full-screen" in Teams
|
||||||
*/
|
*/
|
||||||
private _calculateHeight(): number {
|
private _calculateHeight(): number {
|
||||||
if(this._isRunningInTeams()) {
|
if (this._isRunningInTeams()) {
|
||||||
return window.innerHeight - 30;
|
return window.innerHeight - 30;
|
||||||
} else {
|
} else {
|
||||||
return 600;
|
return 600;
|
||||||
|
@ -138,7 +141,7 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
||||||
private _isPersonalTab() {
|
private _isPersonalTab() {
|
||||||
let _isPersonalTab: Boolean = false;
|
let _isPersonalTab: Boolean = false;
|
||||||
|
|
||||||
if(this._isRunningInTeams() && !this.props.teamsContext.teamId) {
|
if (this._isRunningInTeams() && !this.props.teamsContext.teamId) {
|
||||||
_isPersonalTab = true;
|
_isPersonalTab = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,10 +174,10 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
||||||
* @param info Information about the current active view
|
* @param info Information about the current active view
|
||||||
*/
|
*/
|
||||||
private _datesRender(info: any) {
|
private _datesRender(info: any) {
|
||||||
if(this.calendar.value) {
|
if (this.calendar.value) {
|
||||||
|
|
||||||
// If the active view has changed
|
// If the active view has changed
|
||||||
if((this.state.currentActiveStartDate && this.state.currentActiveEndDate) && this.state.currentActiveStartDate.toString() != info.view.activeStart.toString() && this.state.currentActiveEndDate.toString() != info.view.activeEnd.toString()) {
|
if ((this.state.currentActiveStartDate && this.state.currentActiveEndDate) && this.state.currentActiveStartDate.toString() != info.view.activeStart.toString() && this.state.currentActiveEndDate.toString() != info.view.activeEnd.toString()) {
|
||||||
this._loadEvents(info.view.activeStart, info.view.activeEnd);
|
this._loadEvents(info.view.activeStart, info.view.activeEnd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,13 +187,89 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
||||||
* Handles the resize event when in Microsoft Teams to ensure a proper responsive behaviour
|
* Handles the resize event when in Microsoft Teams to ensure a proper responsive behaviour
|
||||||
*/
|
*/
|
||||||
private _handleResize() {
|
private _handleResize() {
|
||||||
if(this._isRunningInTeams()) {
|
if (this._isRunningInTeams()) {
|
||||||
this.setState({
|
this.setState({
|
||||||
height: window.innerHeight - 30
|
height: window.innerHeight - 30
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert data to Array<EventInput>
|
||||||
|
* @param data Events from API
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Loads the Events based on the current state of the Calendar
|
||||||
* @param startDate The first visible date on the calendar
|
* @param startDate The first visible date on the calendar
|
||||||
|
@ -199,61 +278,129 @@ export default class GraphCalendar extends React.Component<IGraphCalendarProps,
|
||||||
private _loadEvents(startDate: Date, endDate: Date): void {
|
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 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) {
|
if (this.state.groupId || this.state.tabType == TabType.PersonalTab) {
|
||||||
|
var events: Array<EventInput> = new Array<EventInput>();
|
||||||
|
|
||||||
this.props.context.msGraphClientFactory
|
this.props.context.msGraphClientFactory
|
||||||
.getClient()
|
.getClient()
|
||||||
.then((client: MSGraphClient): void => {
|
.then((client: MSGraphClient): void => {
|
||||||
|
let apiUrl: string = `/groups/${this.state.groupId}/events`;
|
||||||
|
if (this._isPersonalTab()) {
|
||||||
|
apiUrl = '/me/events';
|
||||||
|
}
|
||||||
|
|
||||||
let apiUrl: string = `/groups/${this.state.groupId}/events`;
|
client
|
||||||
if(this._isPersonalTab()) {
|
.api(apiUrl)
|
||||||
apiUrl = '/me/events';
|
.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) => {
|
||||||
|
|
||||||
client
|
if (err) {
|
||||||
.api(apiUrl)
|
console.error(err);
|
||||||
.version("v1.0")
|
return;
|
||||||
.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) {
|
//Transform API data to Array<EventInput>
|
||||||
console.error(err);
|
events = this._transformEvents(res);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var events: Array<EventInput> = new Array<EventInput>();
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
res.value.map((item: any) => {
|
// Sets the state with current active calendar dates
|
||||||
// Build a Timezone enabled Date
|
this.setState({
|
||||||
let currentStartDate = moment.tz(item.start.dateTime, item.start.timeZone);
|
currentActiveStartDate: startDate,
|
||||||
let currentEndDate = moment.tz(item.end.dateTime, item.end.timeZone);
|
currentActiveEndDate: endDate,
|
||||||
|
currentSelectedEvent: null
|
||||||
// Adding all retrieved events to the result array
|
|
||||||
events.push({
|
|
||||||
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
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
// Sets the state with the retrieved events and current active calendar dates
|
|
||||||
this.setState({
|
|
||||||
events: events,
|
|
||||||
currentActiveStartDate: startDate,
|
|
||||||
currentActiveEndDate: endDate,
|
|
||||||
currentSelectedEvent: null
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
export interface IGraphCalendarProps {
|
||||||
limit: number;
|
limit: number;
|
||||||
|
showRecurrence: boolean;
|
||||||
context: WebPartContext;
|
context: WebPartContext;
|
||||||
teamsContext: microsoftTeams.Context;
|
teamsContext: microsoftTeams.Context;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue