added filter by category to events.

#3660
#5175
This commit is contained in:
Mohammad Amer 2024-06-28 10:20:29 +03:00
parent 6537fa4c22
commit 582b805eea
19 changed files with 13957 additions and 32113 deletions

View File

@ -1 +0,0 @@
v14.17.6

View File

@ -1,10 +1,6 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
"initialPage": "https://cotoso.sharepoint.com/_layouts/workbench.aspx"
}

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,9 @@
"main": "lib/index.js",
"version": "1.0.12",
"private": true,
"engines": {
"node": ">=12.13.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
@ -23,39 +26,40 @@
"@pnp/spfx-property-controls": "1.14.1",
"@types/draft-js": "^0.10.30",
"@types/globalize": "0.0.34",
"@types/jquery": "^3.3.29",
"@types/jquery": "3.5.30",
"@types/react-big-calendar": "^0.24.6",
"@uifabric/fluent-theme": "^0.16.7",
"browserslist": "^4.12.0",
"browserslist": "4.23.1",
"draft-js": "^0.10.5",
"draftjs-to-html": "^0.8.4",
"globalize": "^1.4.2",
"immutable": "^4.0.0-rc.12",
"jquery": "^3.5.0",
"moment": "^2.29.2",
"immutable": "4.3.6",
"jquery": "3.7.1",
"moment": "2.30.1",
"office-ui-fabric-react": "7.156.0",
"react": "16.9.0",
"react-big-calendar": "^0.24.6",
"react-dom": "16.9.0",
"react-draft-wysiwyg": "^1.13.2",
"react-draft-wysiwyg": "1.15.0",
"react-select": "^4.3.1",
"spfx-uifabric-themes": "^0.6.0",
"string-format": "^2.0.0",
"typescript": "^3.2.4",
"xml2js": "^0.5.0"
"typescript": "3.9.10",
"xml2js": "^0.4.19"
},
"resolutions": {
"@types/react": "16.7.22"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-3.9": "0.4.47",
"@microsoft/sp-build-web": "1.18.2",
"@microsoft/sp-build-web": "1.12.1",
"@microsoft/sp-module-interfaces": "1.12.1",
"@microsoft/sp-tslint-rules": "1.12.1",
"@microsoft/sp-webpart-workbench": "1.12.1",
"@types/es6-promise": "0.0.33",
"@types/react": "16.9.36",
"@types/react-dom": "16.9.8",
"@types/webpack-env": "1.13.1",
"@types/xml2js": "^0.4.4",
"@types/xml2js": "0.4.14",
"@voitanos/jest-preset-spfx-react16": "^1.1.0",
"ajv": "~5.2.2",
"gulp": "4.0.2",
@ -63,8 +67,11 @@
"gulp-stylelint": "^8.0.0",
"jest": "^23.6.0",
"karma-junit-reporter": "^1.2.0",
"spfx-uifabric-themes": "^0.6.0",
"tslint-microsoft-contrib": "6.2.0",
"stylelint": "^9.10.1",
"stylelint-config-standard": "^18.2.0",
"stylelint-scss": "^3.5.4",
"tslint": "^6.1.3",
"tslint-microsoft-contrib": "^6.2.0",
"webpack-bundle-analyzer": "^3.1.0"
},
"solution": {

View File

@ -0,0 +1,73 @@
import * as strings from "CalendarWebPartStrings";
import { IDatePickerStrings } from "office-ui-fabric-react";
export const Constants = {
CategoryColumn: "Category",
MetaDataFieldType: "SP.Taxonomy.TaxonomyFieldValue",
EventResult_LocalStorage: "eventResult",
CalendarEventsWithLocalTime_LocalStorage: "calendarEventsWithLocalTime",
AndConditionStart: "<And>",
AndConditionEnd: "</And>",
OrConditionStart: "<Or>",
OrConditionEnd: "</Or>",
latitude: 58.485601,
longitude: 19.807854,
eventLayoutOverviewPageURL: `{0}/_layouts/15/Event.aspx?ListGuid={1}&ItemId={2}`
};
export const DayPickerStrings: IDatePickerStrings = {
months: [
strings.January,
strings.February,
strings.March,
strings.April,
strings.May,
strings.June,
strings.July,
strings.August,
strings.September,
strings.October,
strings.November,
strings.December,
],
shortMonths: [
strings.Jan,
strings.Feb,
strings.Mar,
strings.Apr,
strings.May,
strings.Jun,
strings.Jul,
strings.Aug,
strings.Sep,
strings.Oct,
strings.Nov,
strings.Dez,
],
days: [
strings.Sunday,
strings.Monday,
strings.Tuesday,
strings.Wednesday,
strings.Thursday,
strings.Friday,
strings.Saturday,
],
shortDays: [
strings.ShortDay_S,
strings.ShortDay_M,
strings.ShortDay_T,
strings.ShortDay_W,
strings.ShortDay_Thursday,
strings.ShortDay_Friday,
strings.ShortDay_Sunday,
],
goToToday: strings.GoToDay,
prevMonthAriaLabel: strings.PrevMonth,
nextMonthAriaLabel: strings.NextMonth,
prevYearAriaLabel: strings.PrevYear,
nextYearAriaLabel: strings.NextYear,
closeButtonAriaLabel: strings.CloseDate,
isRequiredErrorMessage: strings.IsRequired,
invalidInputErrorMessage: strings.InvalidDateFormat,
};

View File

@ -0,0 +1,4 @@
export interface IOption {
key: string;
text: string;
}

View File

@ -10,6 +10,9 @@ import * as moment from 'moment';
import { SiteUser } from "@pnp/sp/src/siteusers";
import { IUserPermissions } from './IUserPermissions';
import parseRecurrentEvent from './parseRecurrentEvent';
import { IComboBoxOption } from '@fluentui/react';
import { Constants } from "../common/Constants";
import { Text } from "@microsoft/sp-core-library";
// Class Services
export default class spservices {
@ -450,7 +453,7 @@ export default class spservices {
* @returns {Promise< IEventData[]>}
* @memberof spservices
*/
public async getEvents(siteUrl: string, listId: string, eventStartDate: Date, eventEndDate: Date): Promise<IEventData[]> {
public async getEvents(siteUrl: string, listId: string, eventStartDate: Date, eventEndDate: Date, categories: IComboBoxOption[]): Promise<IEventData[]> {
let events: IEventData[] = [];
if (!siteUrl) {
@ -463,28 +466,13 @@ export default class spservices {
for (const cat of categoryDropdownOption) {
categoryColor.push({ category: cat.text, color: await this.colorGenerate() });
}
let camlQueryExpression = this.setUpQueryExpression(eventStartDate, eventEndDate, categories);
const web = new Web(siteUrl);
const results = await web.lists.getById(listId).usingCaching().renderListDataAsStream(
{
DatesInUtc: true,
ViewXml: `<View><ViewFields><FieldRef Name='RecurrenceData'/><FieldRef Name='Duration'/><FieldRef Name='Author'/><FieldRef Name='Category'/><FieldRef Name='Description'/><FieldRef Name='ParticipantsPicker'/><FieldRef Name='Geolocation'/><FieldRef Name='ID'/><FieldRef Name='EndDate'/><FieldRef Name='EventDate'/><FieldRef Name='ID'/><FieldRef Name='Location'/><FieldRef Name='Title'/><FieldRef Name='fAllDayEvent'/><FieldRef Name='EventType'/><FieldRef Name='UID' /><FieldRef Name='fRecurrence' /></ViewFields>
<Query>
<Where>
<And>
<Geq>
<FieldRef Name='EventDate' />
<Value IncludeTimeValue='false' Type='DateTime'>${moment(eventStartDate).format('YYYY-MM-DD')}</Value>
</Geq>
<Leq>
<FieldRef Name='EventDate' />
<Value IncludeTimeValue='false' Type='DateTime'>${moment(eventEndDate).format('YYYY-MM-DD')}</Value>
</Leq>
</And>
</Where>
</Query>
<RowLimit Paged=\"FALSE\">2000</RowLimit>
</View>`
ViewXml: camlQueryExpression
}
);
@ -571,6 +559,86 @@ export default class spservices {
}
}
/**
*
* @private
* @param {Date} eventStartDate
* @param {Date} eventEndDate
* @param {IOption[]} departments
* @returns {string} camlQuery
* @memberof spservices
*/
private setUpQueryExpression(
eventStartDate: Date,
eventEndDate: Date,
categories: IComboBoxOption[]
) {
let camlQuery = `
<View>
<ViewFields>
<FieldRef Name='RecurrenceData'/>
<FieldRef Name='Duration'/>
<FieldRef Name='Author'/>
<FieldRef Name='Category'/>
<FieldRef Name='Description'/>
<FieldRef Name='ParticipantsPicker'/>
<FieldRef Name='Geolocation'/>
<FieldRef Name='ID'/>
<FieldRef Name='EndDate'/>
<FieldRef Name='EventDate'/>
<FieldRef Name='Location'/>
<FieldRef Name='Title'/>
<FieldRef Name='fAllDayEvent'/>
<FieldRef Name='EventType'/>
<FieldRef Name='UID' />
<FieldRef Name='fRecurrence' />
</ViewFields>
<Query>
<Where>
<And>
<Geq>
<FieldRef Name='EventDate' />
<Value IncludeTimeValue='false' Type='DateTime'>${moment(eventStartDate).format("YYYY-MM-DD")}</Value>
</Geq>
{0}
<Leq>
<FieldRef Name='EventDate' />
<Value IncludeTimeValue='false' Type='DateTime'>${moment(eventEndDate).format("YYYY-MM-DD")}</Value>
</Leq>
{1}
{2}
</And>
</Where>
</Query>
<RowLimit Paged=\"FALSE\">2000</RowLimit>
</View>`;
let categoryCondition = `
<Eq>
<FieldRef Name='Category' />
<Value Type='Choice'>{0}</Value>
</Eq>`;
const deptsLength: number = categories.length;
let queryResult: string = "";
if (deptsLength > 0) {
if (deptsLength == 1) {
return Text.format(camlQuery, Constants.AndConditionStart, Text.format(categoryCondition, categories[0].key), Constants.AndConditionEnd);
} else {
let orCondition: string = `${Constants.OrConditionStart}{0}{1}${Constants.OrConditionEnd}`;
queryResult = Text.format(orCondition, Text.format(categoryCondition, categories[0].key), Text.format(categoryCondition, categories[1]));
for (let i = 2; i < categories.length; i++) {
const category = categories[i];
queryResult = Text.format(orCondition, Text.format(categoryCondition, category.key), queryResult);
}
}
return Text.format(camlQuery, Constants.AndConditionStart, queryResult, Constants.AndConditionEnd);
}
return Text.format(camlQuery, "", queryResult, "");
}
/**
*
* @private

View File

@ -11,8 +11,8 @@ import {
} from '@microsoft/sp-property-pane';
import * as strings from 'CalendarWebPartStrings';
import Calendar from './components/Calendar';
import { ICalendarProps } from './components/ICalendarProps';
import Calendar from './components/Calendar/Calendar';
import { ICalendarProps } from './components/Calendar/ICalendarProps';
import { PropertyFieldDateTimePicker, DateConvention, TimeConvention, IDateTimeFieldValue } from '@pnp/spfx-property-controls/lib/PropertyFieldDateTimePicker';
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";

View File

@ -1,7 +1,7 @@
@import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
@import "~office-ui-fabric-react/dist/sass/References.scss";
@import "./node_modules/spfx-uifabric-themes/office.theme.vars";
@import "../../themes";
@import "../../../themes";
:export {
/* stylelint-disable property-case */

View File

@ -8,7 +8,7 @@ import * as strings from 'CalendarWebPartStrings';
import 'react-big-calendar/lib/css/react-big-calendar.css';
require('./calendar.css');
import { CommunicationColors, FluentCustomizations, FluentTheme } from '@uifabric/fluent-theme';
import Year from './Year';
import Year from '../Year/Year';
import { Calendar as MyCalendar, momentLocalizer } from 'react-big-calendar';
@ -36,22 +36,23 @@ import {
Spinner,
SpinnerSize,
MessageBar,
MessageBarType,
MessageBarType
} from 'office-ui-fabric-react';
import { IComboBoxOption, SelectableOptionMenuItemType } from '@fluentui/react';
import { EnvironmentType } from '@microsoft/sp-core-library';
import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { DisplayMode } from '@microsoft/sp-core-library';
import spservices from '../../../services/spservices';
import spservices from '../../../../services/spservices';
import { stringIsNullOrEmpty } from '@pnp/common';
import { Event } from '../../../controls/Event/event';
import { IPanelModelEnum } from '../../../controls/Event/IPanelModeEnum';
import { IEventData } from './../../../services/IEventData';
import { IUserPermissions } from './../../../services/IUserPermissions';
import { Event } from '../../../../controls/Event/event';
import { IPanelModelEnum } from '../../../../controls/Event/IPanelModeEnum';
import { IEventData } from './../../../../services/IEventData';
import { IUserPermissions } from './../../../../services/IUserPermissions';
import Category from '../Category/Category';
import { IOption } from '../../../../services/IOption';
//const localizer = BigCalendar.momentLocalizer(moment);
@ -64,11 +65,14 @@ const localizer = momentLocalizer(moment);
export default class Calendar extends React.Component<ICalendarProps, ICalendarState> {
private spService: spservices = null;
private userListPermissions: IUserPermissions = undefined;
public constructor(props) {
super(props);
this.state = {
showDialog: false,
categories: [],
selectedCategories: [],
eventData: [],
selectedEvent: undefined,
isloading: true,
@ -79,9 +83,29 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
this.onDismissPanel = this.onDismissPanel.bind(this);
this.onSelectEvent = this.onSelectEvent.bind(this);
this.onSelectSlot = this.onSelectSlot.bind(this);
this.onChangeCategories = this.onChangeCategories.bind(this);
this.spService = new spservices(this.props.context);
moment.locale(this.props.context.pageContext.cultureInfo.currentUICultureName);
}
/**
* @private
* @param {*} selectedCatogries
* @memberof Calendar
*/
private async onChangeCategories(selectedCategories: IComboBoxOption[]) {
try {
this.setState({
selectedCategories: selectedCategories
});
await this.loadEvents();
} catch (error) {
this.setState({
hasError: true,
errorMessage: error.message,
isloading: false,
});
}
}
private onDocumentCardClick(ev: React.SyntheticEvent<HTMLElement, Event>) {
@ -118,12 +142,20 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
*/
private async loadEvents() {
try {
// Teste Properties
if (!this.props.list || !this.props.siteUrl || !this.props.eventStartDate.value || !this.props.eventEndDate.value) return;
this.userListPermissions = await this.spService.getUserPermissions(this.props.siteUrl, this.props.list);
const eventsData: IEventData[] = await this.spService.getEvents(escape(this.props.siteUrl), escape(this.props.list), this.props.eventStartDate.value, this.props.eventEndDate.value);
let eventsData: IEventData[] = [];
if (this.state.selectedCategories.length > 0) {
eventsData = await this.spService.getEvents(
escape(this.props.siteUrl),
escape(this.props.list),
this.props.eventStartDate.value,
this.props.eventEndDate.value,
this.state.selectedCategories
);
}
this.setState({ eventData: eventsData, hasError: false, errorMessage: "" });
} catch (error) {
@ -134,7 +166,9 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
* @memberof Calendar
*/
public async componentDidMount() {
this.setState({ isloading: true });
const categories: IOption[] = await this.spService.getChoiceFieldOptions(this.props.siteUrl, this.props.list, 'Category');
this.setState({ isloading: true, categories: categories, selectedCategories: categories});
await this.loadEvents();
this.setState({ isloading: false });
}
@ -296,7 +330,6 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
};
}
/**
*
* @param {*} date
@ -342,6 +375,11 @@ export default class Calendar extends React.Component<ICalendarProps, ICalendarS
<div>
{this.state.isloading ? <Spinner size={SpinnerSize.large} label={strings.LoadingEventsLabel} /> :
<div className={styles.container}>
<Category
catogries={this.state.categories}
selectedCategories={this.state.selectedCategories}
onChangeCategories={this.onChangeCategories}
></Category>
<MyCalendar
dayPropGetter={this.dayPropGetter}
localizer={localizer}

View File

@ -0,0 +1,16 @@
import { IPanelModelEnum} from '../../../../controls/Event/IPanelModeEnum';
import { IEventData } from './../../../../services/IEventData';
import { IComboBoxOption } from '@fluentui/react';
export interface ICalendarState {
showDialog: boolean;
categories: IComboBoxOption[];
selectedCategories: IComboBoxOption[];
eventData: IEventData[];
selectedEvent: IEventData;
panelMode?: IPanelModelEnum;
startDateSlot?: Date;
endDateSlot?:Date;
isloading: boolean;
hasError: boolean;
errorMessage: string;
}

View File

@ -0,0 +1,80 @@
import React from 'react';
import { ICategoryProps, ICategoryState } from "./ICategory";
import { ComboBox, IComboBoxOption, SelectableOptionMenuItemType } from '@fluentui/react';
export default class Category extends React.PureComponent<ICategoryProps, ICategoryState> {
private selectableOptions = this.props.catogries.filter(
option =>
(option.itemType === SelectableOptionMenuItemType.Normal || option.itemType === undefined) && !option.disabled,
);
public constructor(props) {
super(props);
this.state = {
selectedKeys: this.props.selectedCategories.length == 0 ?
[String('selectAll'), ...this.props.catogries.map(o => o.key as string)] :
[String('selectAll'), ...this.props.selectedCategories.map(o => o.key as string)]
};
}
private onChange = (event, option, index, value) => {
const selected = option?.selected;
const { selectedKeys } = this.state;
const currentSelectedOptionKeys = selectedKeys.filter(key => key !== 'selectAll');
const selectAllState = currentSelectedOptionKeys.length === this.selectableOptions.length;
if (option) {
if (option?.itemType === SelectableOptionMenuItemType.SelectAll) {
selectAllState
? this.setState({ selectedKeys: [] }, () => { this.updateSelectableCategories(); })
: this.setState({ selectedKeys: ['selectAll', ...this.selectableOptions.map(o => o.key as string)] }, () => { this.updateSelectableCategories(); });
}
else {
const updatedKeys = selected
? [...currentSelectedOptionKeys, option!.key as string]
: currentSelectedOptionKeys.filter(k => k !== option.key);
if (updatedKeys.length === this.selectableOptions.length) {
updatedKeys.push('selectAll');
}
this.setState({ selectedKeys: updatedKeys, }, () => { this.updateSelectableCategories(); });
}
}
}
private updateSelectableCategories() {
const currentSelectedCategories: IComboBoxOption[] = [];
if (this.state.selectedKeys.length >= 0) {
this.state.selectedKeys.forEach(key => {
const category: IComboBoxOption[] = this.selectableOptions.filter(opt => opt.key === key);
if (category.length > 0) {
currentSelectedCategories.push(category[0]);
}
});
}
this.props.onChangeCategories(currentSelectedCategories);
}
public render(): React.ReactElement {
return (
<div>
<ComboBox
label="Select Category"
multiSelect
options={[
{ key: 'selectAll', text: 'Select All', itemType: SelectableOptionMenuItemType.SelectAll },
...this.props.catogries
]}
selectedKey={this.state.selectedKeys}
onChange={this.onChange}
/>
</div>
);
}
}

View File

@ -0,0 +1,10 @@
import { IComboBoxOption } from '@fluentui/react';
export interface ICategoryProps {
catogries: IComboBoxOption[];
selectedCategories: IComboBoxOption[];
onChangeCategories: (onChangeCategories: IComboBoxOption[]) => void;
}
export interface ICategoryState {
selectedKeys: string[];
}

View File

@ -1,13 +0,0 @@
import { IPanelModelEnum} from '../../../controls/Event/IPanelModeEnum';
import { IEventData } from './../../../services/IEventData';
export interface ICalendarState {
showDialog: boolean;
eventData: IEventData[];
selectedEvent: IEventData;
panelMode?: IPanelModelEnum;
startDateSlot?: Date;
endDateSlot?:Date;
isloading: boolean;
hasError: boolean;
errorMessage: string;
}

View File

@ -20,7 +20,8 @@
"./node_modules/@microsoft"
],
"types": [
"webpack-env"
"webpack-env",
"@types/jest"
],
"lib": [
"es5",