React Quick Poll (#1148)
* Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * Updated sample YAML description * React Quick Poll with simple and easy config. Co-authored-by: Sylvia Okafor <sokafor@microsoft.com>
This commit is contained in:
parent
faddec698f
commit
76ea322aac
|
@ -0,0 +1,25 @@
|
||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
|
||||||
|
[*]
|
||||||
|
|
||||||
|
# change these settings to your own preference
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# we recommend you to keep these unchanged
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[{package,bower}.json]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Build generated files
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
solution
|
||||||
|
temp
|
||||||
|
*.sppkg
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Visual Studio files
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
.vs
|
||||||
|
bin
|
||||||
|
obj
|
||||||
|
|
||||||
|
# Resx Generated Code
|
||||||
|
*.resx.ts
|
||||||
|
|
||||||
|
# Styles Generated Code
|
||||||
|
*.scss.ts
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"environment": "spo",
|
||||||
|
"version": "1.10.0",
|
||||||
|
"libraryName": "react-simple-poll",
|
||||||
|
"libraryId": "890ced10-dacc-4d0d-b9df-355a289980b3",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
# React Quick Poll
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
> This component is developed for the users who really need to create a **_Poll_** within a minute and with less maintenance. **_'QuickPoll'_** list will be created automatically to store the user response.
|
||||||
|
> Following are some of the features of this component.
|
||||||
|
* **_Easy_** to setup with most of the configurations are optional.
|
||||||
|
* **_Flexible_** to use without any critical configuration.
|
||||||
|
* More than one poll questions can be added.
|
||||||
|
* Schedule the poll questions in **_advance_** using the **_date_** parameters.
|
||||||
|
* Option to choose the poll that has to be visible to the end users.
|
||||||
|
* Poll response can be viewed via graphical representation as **_charts_**.
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
1. **_Display poll based on date_** - This property will check for the **'Start Date'** and **'End Date'** on the poll questions to display the correct poll question to the end user. The **'Start Date'** and **'End Date'** on the poll question will be enabled only when this property is turned on.
|
||||||
|
|
||||||
|
2. **_Poll Questions_** - Manage the collection of poll questions and choices.
|
||||||
|
|
||||||
|
* **_Question Title_** - Title of the question.
|
||||||
|
* **_Choices_** - Choices separated by comma.
|
||||||
|
* **_Multi Selection_** - Whether the users are allowed to choose one or multiple.
|
||||||
|
* **_Start Date_** - Date when the end user can start seeing the poll question.
|
||||||
|
* **_End Date_** - Last day of the poll question visible to the end user.
|
||||||
|
|
||||||
|
3. **_Success Message_** - Message to be displayed to the user after successfull submission. It is optional, if not provided the default message '**Thank you for your submission**' will be displayed.
|
||||||
|
|
||||||
|
4. **_Response Message_** - Message to be displayed to the user with the user response, once the user has submitted. It is optional, if not provided the default message '**You voted for: ~User Response~**' will be displayed below the chart.
|
||||||
|
|
||||||
|
5. **_Submit button text_** - Text to be displayed on the submit button. It is optional, if not provided the default text '**Submit Vote**' will be displayed.
|
||||||
|
|
||||||
|
6. **_Preferred Chart Type_** - Chart type to display the overall response for the question.
|
||||||
|
|
||||||
|
### _Note_
|
||||||
|
* Poll questions with the same **'Start Date'** and **'End Date'** will follow the sort order to display the latest question to the end user.
|
||||||
|
* Once the user started to response to the poll, do not delete the question from the question collection. All the questions are mapped based on the **ID** auto generated. It cannot be recovered once deleted.
|
||||||
|
* Make sure the **Multi Choice** option is chosen wisely, do not change once the user started to response to the poll.
|
||||||
|
|
||||||
|
## Preview
|
||||||
|
![Advanced-Comments-Box](./assets/react-quick-poll.gif)
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||||
|
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
> **@microsoft/generator-sharepoint - 1.10.0**
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-quick-poll | Sudharsan K.([@sudharsank](https://twitter.com/sudharsank), [Know More](http://windowssharepointserver.blogspot.com/))
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0.0.1|Feb 24 2020|Initial release
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||||
|
|
||||||
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
|
- Clone this repository
|
||||||
|
- in the command line run:
|
||||||
|
- `npm install`
|
||||||
|
- `gulp bundle --ship && gulp package-solution --ship`
|
||||||
|
- Add the .sppkg file to the app catalog and add the **'_Quick Poll_'** web part to the page.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Used [PnP Property Pane Controls](https://sharepoint.github.io/sp-dev-fx-property-controls/) to create the property pane controls
|
||||||
|
* [PropertyFieldToggleWithCallout](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldToggleWithCallout/)
|
||||||
|
* [PropertyFieldCollectionData](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldCollectionData/)
|
||||||
|
* [PropertyFieldChoiceGroupWithCallout](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldChoiceGroupWithCallout/)
|
||||||
|
* PropertyPaneTextField (From base property controls)
|
||||||
|
- Used [PnP Reusable REact Controls](https://sharepoint.github.io/sp-dev-fx-controls-react/)
|
||||||
|
* [Placeholder](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/Placeholder/)
|
||||||
|
* [ChartControl](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ChartControl/)
|
||||||
|
- Used few styles and controls (Text, MessageBar, ProgressIndicator, PrimaryButton, ChoiceGroup, List, Checkbox) from [Office UI Fabric](https://developer.microsoft.com/en-us/fabric)
|
||||||
|
- Used [PnP](https://pnp.github.io/pnpjs/) for communication with SharePoint.
|
||||||
|
- Used [Moment.js](https://momentjs.com/) for datetime formatting.
|
||||||
|
|
||||||
|
#### Local Mode
|
||||||
|
This solution doesn't work on local mode.
|
||||||
|
|
||||||
|
#### SharePoint Mode
|
||||||
|
If you want to try on a real environment, open:
|
||||||
|
[O365 Workbench](https://your-domain.sharepoint.com/_layouts/15/workbench.aspx)
|
Binary file not shown.
After Width: | Height: | Size: 5.3 MiB |
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"simple-poll-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/simplePoll/SimplePollWebPart.js",
|
||||||
|
"manifest": "./src/webparts/simplePoll/SimplePollWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"SimplePollWebPartStrings": "lib/webparts/simplePoll/loc/{locale}.js",
|
||||||
|
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js",
|
||||||
|
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||||
|
"deployCdnPath": "temp/deploy"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||||
|
"workingDir": "./temp/deploy/",
|
||||||
|
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||||
|
"container": "react-simple-poll",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "React Quick Poll",
|
||||||
|
"id": "890ced10-dacc-4d0d-b9df-355a289980b3",
|
||||||
|
"version": "1.0.0.1",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"isDomainIsolated": false
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/react-quick-poll.sppkg"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||||
|
"port": 4321,
|
||||||
|
"https": true,
|
||||||
|
"initialPage": "https://localhost:5432/workbench",
|
||||||
|
"api": {
|
||||||
|
"port": 5432,
|
||||||
|
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||||
|
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const build = require('@microsoft/sp-build-web');
|
||||||
|
|
||||||
|
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||||
|
|
||||||
|
build.initialize(require('gulp'));
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "react-simple-poll",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/sp-core-library": "1.10.0",
|
||||||
|
"@microsoft/sp-lodash-subset": "1.10.0",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
|
||||||
|
"@microsoft/sp-property-pane": "1.10.0",
|
||||||
|
"@microsoft/sp-webpart-base": "1.10.0",
|
||||||
|
"@pnp/sp": "^2.0.2",
|
||||||
|
"@pnp/spfx-controls-react": "^1.16.0",
|
||||||
|
"@pnp/spfx-property-controls": "^1.16.0",
|
||||||
|
"@types/es6-promise": "0.0.33",
|
||||||
|
"@types/react": "16.8.8",
|
||||||
|
"@types/react-dom": "16.8.3",
|
||||||
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"office-ui-fabric-react": "6.189.2",
|
||||||
|
"react": "16.8.5",
|
||||||
|
"react-dom": "16.8.5"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@types/react": "16.8.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/sp-build-web": "1.10.0",
|
||||||
|
"@microsoft/sp-tslint-rules": "1.10.0",
|
||||||
|
"@microsoft/sp-module-interfaces": "1.10.0",
|
||||||
|
"@microsoft/sp-webpart-workbench": "1.10.0",
|
||||||
|
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
|
||||||
|
"gulp": "~3.9.1",
|
||||||
|
"@types/chai": "3.4.34",
|
||||||
|
"@types/mocha": "2.2.38",
|
||||||
|
"ajv": "~5.2.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
import { sp } from "@pnp/sp";
|
||||||
|
import "@pnp/sp/webs";
|
||||||
|
import "@pnp/sp/lists/web";
|
||||||
|
import "@pnp/sp/folders/web";
|
||||||
|
import "@pnp/sp/files/folder";
|
||||||
|
import "@pnp/sp/items/list";
|
||||||
|
import "@pnp/sp/fields/list";
|
||||||
|
import "@pnp/sp/views/list";
|
||||||
|
import "@pnp/sp/site-users/web";
|
||||||
|
import { IList } from "@pnp/sp/lists";
|
||||||
|
import { IItemAddResult } from "@pnp/sp/items";
|
||||||
|
import * as _ from "lodash";
|
||||||
|
import { IUserInfo, IResponseDetails } from "../Models";
|
||||||
|
|
||||||
|
export default class SPHelper {
|
||||||
|
private selectFields: string[] = ["ID", "Title", "QuestionID", "UserResponse"];
|
||||||
|
private _list: IList = null;
|
||||||
|
private lst_response: string = "";
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.lst_response = "QuickPoll";
|
||||||
|
this._list = sp.web.lists.getByTitle(this.lst_response);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the current logged in user information
|
||||||
|
*/
|
||||||
|
public getCurrentUserInfo = async (): Promise<IUserInfo> => {
|
||||||
|
let userinfo: IUserInfo = null;
|
||||||
|
let currentUserInfo = await sp.web.currentUser.get();
|
||||||
|
userinfo = {
|
||||||
|
ID: currentUserInfo.Id.toString(),
|
||||||
|
Email: currentUserInfo.Email,
|
||||||
|
LoginName: currentUserInfo.LoginName,
|
||||||
|
DisplayName: currentUserInfo.Title,
|
||||||
|
Picture: '/_layouts/15/userphoto.aspx?size=S&username=' + currentUserInfo.UserPrincipalName,
|
||||||
|
};
|
||||||
|
return userinfo;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the poll response based on the question id.
|
||||||
|
*/
|
||||||
|
public getPollResponse = async (questionId: string) => {
|
||||||
|
let questionResponse = await this._list.items.select(this.selectFields.join(','))
|
||||||
|
.filter(`QuestionID eq '${questionId}'`).expand('FieldValuesAsText').get();
|
||||||
|
if (questionResponse.length > 0) {
|
||||||
|
var tmpResponse = questionResponse[0].FieldValuesAsText.UserResponse;
|
||||||
|
if (tmpResponse != undefined && tmpResponse != null && tmpResponse !== "") {
|
||||||
|
var jsonQResponse = JSON.parse(tmpResponse);
|
||||||
|
return jsonQResponse;
|
||||||
|
} else return [];
|
||||||
|
} else return [];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add the user response.
|
||||||
|
*/
|
||||||
|
public addPollResponse = async (userResponse: IResponseDetails, allUserResponse: any): Promise<IItemAddResult> => {
|
||||||
|
let addedresponse = await this._list.items.add({
|
||||||
|
Title: userResponse.PollQuestion,
|
||||||
|
QuestionID: userResponse.PollQuestionId,
|
||||||
|
UserResponse: JSON.stringify(allUserResponse)
|
||||||
|
});
|
||||||
|
return addedresponse;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Update the over all response based on the end user response.
|
||||||
|
*/
|
||||||
|
public updatePollResponse = async (questionId: string, allUserResponse: any) => {
|
||||||
|
var response = await this._list.items.select(this.selectFields.join(','))
|
||||||
|
.filter(`QuestionID eq '${questionId}'`).expand('FieldValuesAsText').get();
|
||||||
|
if (response.length > 0) {
|
||||||
|
if (allUserResponse.length > 0) {
|
||||||
|
let updatedResponse = await this._list.items.getById(response[0].ID).update({
|
||||||
|
UserResponse: JSON.stringify(allUserResponse)
|
||||||
|
});
|
||||||
|
return updatedResponse;
|
||||||
|
} else return await this._list.items.getById(response[0].ID).delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Submit the user response.
|
||||||
|
*/
|
||||||
|
public submitResponse = async (userResponse: IResponseDetails): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
let allUserResponse = await this.getPollResponse(userResponse.PollQuestionId);
|
||||||
|
if (allUserResponse.length > 0) {
|
||||||
|
allUserResponse.push({
|
||||||
|
UserID: userResponse.UserID,
|
||||||
|
UserName: userResponse.UserDisplayName,
|
||||||
|
Response: userResponse.PollResponse,
|
||||||
|
MultiResponse: userResponse.PollMultiResponse,
|
||||||
|
});
|
||||||
|
// Update the user response
|
||||||
|
await this.updatePollResponse(userResponse.PollQuestionId, allUserResponse);
|
||||||
|
} else {
|
||||||
|
allUserResponse.push({
|
||||||
|
UserID: userResponse.UserID,
|
||||||
|
UserName: userResponse.UserDisplayName,
|
||||||
|
Response: userResponse.PollResponse,
|
||||||
|
MultiResponse: userResponse.PollMultiResponse,
|
||||||
|
});
|
||||||
|
// Add the user response
|
||||||
|
await this.addPollResponse(userResponse, allUserResponse);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check and create the User response list.
|
||||||
|
*/
|
||||||
|
public checkListExists = async (): Promise<boolean> => {
|
||||||
|
return new Promise<boolean>(async (res, rej) => {
|
||||||
|
sp.web.lists.getByTitle(this.lst_response).get().then((listExists) => {
|
||||||
|
res(true);
|
||||||
|
}).catch(async err => {
|
||||||
|
let listExists = await (await sp.web.lists.ensure(this.lst_response)).list;
|
||||||
|
await listExists.fields.addText('QuestionID', 255, { Required: true, Description: '' });
|
||||||
|
await listExists.fields.addMultilineText('UserResponse', 6, false, false, false, false, { Required: false, Description: '' });
|
||||||
|
let allItemsView = await listExists.views.getByTitle('All Items');
|
||||||
|
await allItemsView.fields.add('QuestionID');
|
||||||
|
await allItemsView.fields.add('UserResponse');
|
||||||
|
res(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
export enum MessageScope{
|
||||||
|
Success = 0,
|
||||||
|
Failure = 1,
|
||||||
|
Warning = 2,
|
||||||
|
Info = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PanelColors{
|
||||||
|
Success = 0,
|
||||||
|
Danger = 1,
|
||||||
|
Warning = 2,
|
||||||
|
Info = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FilterViews{
|
||||||
|
MostPopular,
|
||||||
|
Latest
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { ChartType } from "@pnp/spfx-controls-react/lib/ChartControl";
|
||||||
|
|
||||||
|
export interface IPollAnalyticsInfo {
|
||||||
|
Question: string;
|
||||||
|
Labels: string[];
|
||||||
|
PollResponse: any[];
|
||||||
|
ChartType: ChartType;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export interface IQuestionDetails {
|
||||||
|
Id: string;
|
||||||
|
DisplayName: string;
|
||||||
|
Choices?: string;
|
||||||
|
MultiChoice?: boolean;
|
||||||
|
StartDate: Date;
|
||||||
|
EndDate: Date;
|
||||||
|
UseDate: boolean;
|
||||||
|
SortIdx: number;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export interface IResponseDetails {
|
||||||
|
UserID: string;
|
||||||
|
UserDisplayName: string;
|
||||||
|
UserLoginName?: string;
|
||||||
|
PollResponse?: string;
|
||||||
|
PollMultiResponse?: string[];
|
||||||
|
PollQuestion: string;
|
||||||
|
PollQuestionId: string;
|
||||||
|
IsMulti: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IUserInfo {
|
||||||
|
ID: string;
|
||||||
|
Email: string;
|
||||||
|
LoginName: string;
|
||||||
|
DisplayName: string;
|
||||||
|
Picture: string;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './IQuestionDetails';
|
||||||
|
export * from './IResponseDetails';
|
||||||
|
export * from './IPollAnalyticsInfo';
|
||||||
|
export * from './IUserInfo';
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "346292ca-514f-49ce-91aa-f1bf3b30088b",
|
||||||
|
"alias": "SimplePollWebPart",
|
||||||
|
"componentType": "WebPart",
|
||||||
|
"version": "*",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
"requiresCustomScript": false,
|
||||||
|
"supportedHosts": ["SharePointWebPart"],
|
||||||
|
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||||
|
"group": { "default": "Other" },
|
||||||
|
"title": { "default": "Quick Poll" },
|
||||||
|
"description": { "default": "A quick poll." },
|
||||||
|
"officeFabricIconFontName": "BarChartVerticalEdit",
|
||||||
|
"properties": {
|
||||||
|
"chartType": "doughnut",
|
||||||
|
"pollBasedOnDate": false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version, ServiceScope } from '@microsoft/sp-core-library';
|
||||||
|
import {
|
||||||
|
IPropertyPaneConfiguration,
|
||||||
|
PropertyPaneTextField,
|
||||||
|
} from '@microsoft/sp-property-pane';
|
||||||
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
|
import { CalloutTriggers } from '@pnp/spfx-property-controls/lib/PropertyFieldHeader';
|
||||||
|
import { PropertyFieldToggleWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldToggleWithCallout';
|
||||||
|
import { PropertyFieldChoiceGroupWithCallout } from '@pnp/spfx-property-controls/lib/PropertyFieldChoiceGroupWithCallout';
|
||||||
|
import { PropertyFieldCollectionData, CustomCollectionFieldType } from '@pnp/spfx-property-controls/lib/PropertyFieldCollectionData';
|
||||||
|
import { DateTimePicker, DateConvention, TimeConvention } from '@pnp/spfx-controls-react/lib/DateTimePicker';
|
||||||
|
import { sp } from "@pnp/sp/presets/all";
|
||||||
|
import * as strings from 'SimplePollWebPartStrings';
|
||||||
|
import SimplePoll from './components/SimplePoll';
|
||||||
|
import { ISimplePollProps } from './components/ISimplePollProps';
|
||||||
|
import SPHelper from '../../Common/SPHelper';
|
||||||
|
import { IUserInfo } from '../../Models';
|
||||||
|
import { ChartType } from '@pnp/spfx-controls-react/lib/ChartControl';
|
||||||
|
|
||||||
|
|
||||||
|
export interface ISimplePollWebPartProps {
|
||||||
|
pollQuestions: any[];
|
||||||
|
MsgAfterSubmission: string;
|
||||||
|
BtnSubmitVoteText: string;
|
||||||
|
chartType: ChartType;
|
||||||
|
ResponseMsgToUser: string;
|
||||||
|
pollBasedOnDate: boolean;
|
||||||
|
NoPollMsg: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SimplePollWebPart extends BaseClientSideWebPart<ISimplePollWebPartProps> {
|
||||||
|
private helper: SPHelper = null;
|
||||||
|
private userinfo: IUserInfo = null;
|
||||||
|
protected async onInit(): Promise<void> {
|
||||||
|
await super.onInit();
|
||||||
|
sp.setup(this.context);
|
||||||
|
this.helper = new SPHelper();
|
||||||
|
this.userinfo = await this.helper.getCurrentUserInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
const element: React.ReactElement<ISimplePollProps> = React.createElement(
|
||||||
|
SimplePoll,
|
||||||
|
{
|
||||||
|
pollQuestions: this.properties.pollQuestions,
|
||||||
|
SuccessfullVoteSubmissionMsg: this.properties.MsgAfterSubmission,
|
||||||
|
ResponseMsgToUser: this.properties.ResponseMsgToUser,
|
||||||
|
BtnSubmitVoteText: this.properties.BtnSubmitVoteText,
|
||||||
|
chartType: this.properties.chartType ? this.properties.chartType : ChartType.Doughnut,
|
||||||
|
pollBasedOnDate: this.properties.pollBasedOnDate,
|
||||||
|
NoPollMsg: this.properties.NoPollMsg,
|
||||||
|
currentUserInfo: this.userinfo,
|
||||||
|
openPropertyPane: this.openPropertyPane
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get disableReactivePropertyChanges() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
private openPropertyPane = (): void => {
|
||||||
|
this.context.propertyPane.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: strings.PropertyPaneDescription
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: strings.BasicGroupName,
|
||||||
|
groupFields: [
|
||||||
|
PropertyFieldToggleWithCallout('pollBasedOnDate', {
|
||||||
|
calloutTrigger: CalloutTriggers.Hover,
|
||||||
|
key: 'pollBasedOnDateFieldId',
|
||||||
|
label: strings.PollDateLabel,
|
||||||
|
calloutContent: React.createElement('div', {}, strings.PollDateCalloutText),
|
||||||
|
onText: 'Yes',
|
||||||
|
offText: 'No',
|
||||||
|
checked: this.properties.pollBasedOnDate
|
||||||
|
}),
|
||||||
|
PropertyFieldCollectionData("pollQuestions", {
|
||||||
|
key: "pollQuestions",
|
||||||
|
label: strings.PollQuestionsLabel,
|
||||||
|
panelHeader: strings.PollQuestionsPanelHeader,
|
||||||
|
manageBtnLabel: strings.PollQuestionsManageButton,
|
||||||
|
enableSorting: true,
|
||||||
|
value: this.properties.pollQuestions,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: "QTitle",
|
||||||
|
title: strings.Q_Title_Title,
|
||||||
|
type: CustomCollectionFieldType.custom,
|
||||||
|
required: true,
|
||||||
|
onCustomRender: (field, value, onUpdate, item, itemId) => {
|
||||||
|
return (
|
||||||
|
React.createElement("div", null,
|
||||||
|
React.createElement("textarea",
|
||||||
|
{
|
||||||
|
style: { width: "250px", height: "70px" },
|
||||||
|
placeholder: strings.Q_Title_Placeholder,
|
||||||
|
key: itemId,
|
||||||
|
value: value,
|
||||||
|
onChange: (event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||||
|
onUpdate(field.id, event.currentTarget.value);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "QOptions",
|
||||||
|
title: strings.Q_Options_Title,
|
||||||
|
type: CustomCollectionFieldType.custom,
|
||||||
|
required: true,
|
||||||
|
onCustomRender: (field, value, onUpdate, item, itemId) => {
|
||||||
|
return (
|
||||||
|
React.createElement("div", null,
|
||||||
|
React.createElement("textarea",
|
||||||
|
{
|
||||||
|
style: { width: "250px", height: "70px" },
|
||||||
|
placeholder: strings.Q_Options_Placeholder,
|
||||||
|
key: itemId,
|
||||||
|
value: value,
|
||||||
|
onChange: (event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||||
|
onUpdate(field.id, event.currentTarget.value);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "QMultiChoice",
|
||||||
|
title: strings.MultiChoice_Title,
|
||||||
|
type: CustomCollectionFieldType.boolean,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "QStartDate",
|
||||||
|
title: strings.Q_StartDate_Title,
|
||||||
|
type: CustomCollectionFieldType.custom,
|
||||||
|
required: false,
|
||||||
|
onCustomRender: (field, value, onUpdate, item, itemId) => {
|
||||||
|
return (
|
||||||
|
React.createElement(DateTimePicker, {
|
||||||
|
key: itemId,
|
||||||
|
showLabels: false,
|
||||||
|
dateConvention: DateConvention.Date,
|
||||||
|
showGoToToday: true,
|
||||||
|
showMonthPickerAsOverlay: true,
|
||||||
|
value: value ? new Date(value) : null,
|
||||||
|
disabled: !this.properties.pollBasedOnDate,
|
||||||
|
onChange: (date: Date) => {
|
||||||
|
onUpdate(field.id, date);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "QEndDate",
|
||||||
|
title: strings.Q_EndDate_Title,
|
||||||
|
type: CustomCollectionFieldType.custom,
|
||||||
|
required: false,
|
||||||
|
onCustomRender: (field, value, onUpdate, item, itemId) => {
|
||||||
|
return (
|
||||||
|
React.createElement(DateTimePicker, {
|
||||||
|
key: itemId,
|
||||||
|
showLabels: false,
|
||||||
|
dateConvention: DateConvention.Date,
|
||||||
|
showGoToToday: true,
|
||||||
|
showMonthPickerAsOverlay: true,
|
||||||
|
value: value ? new Date(value) : null,
|
||||||
|
disabled: !this.properties.pollBasedOnDate,
|
||||||
|
onChange: (date: Date) => {
|
||||||
|
onUpdate(field.id, date);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
disabled: false
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('MsgAfterSubmission', {
|
||||||
|
label: strings.MsgAfterSubmissionLabel,
|
||||||
|
description: strings.MsgAfterSubmissionDescription,
|
||||||
|
maxLength: 150,
|
||||||
|
multiline: true,
|
||||||
|
rows: 3,
|
||||||
|
resizable: false,
|
||||||
|
placeholder: strings.MsgAfterSubmissionPlaceholder,
|
||||||
|
value: this.properties.MsgAfterSubmission
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('ResponseMsgToUser', {
|
||||||
|
label: strings.ResponseMsgToUserLabel,
|
||||||
|
description: strings.ResponseMsgToUserDescription,
|
||||||
|
maxLength: 150,
|
||||||
|
multiline: true,
|
||||||
|
rows: 3,
|
||||||
|
resizable: false,
|
||||||
|
placeholder: strings.ResponseMsgToUserPlaceholder,
|
||||||
|
value: this.properties.ResponseMsgToUser
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('BtnSubmitVoteText', {
|
||||||
|
label: strings.BtnSumbitVoteLabel,
|
||||||
|
description: strings.BtnSumbitVoteDescription,
|
||||||
|
maxLength: 50,
|
||||||
|
multiline: false,
|
||||||
|
resizable: false,
|
||||||
|
placeholder: strings.BtnSumbitVotePlaceholder,
|
||||||
|
value: this.properties.BtnSubmitVoteText
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('NoPollMsg', {
|
||||||
|
label: strings.NoPollMsgLabel,
|
||||||
|
description: strings.NoPollMsgDescription,
|
||||||
|
maxLength: 150,
|
||||||
|
multiline: true,
|
||||||
|
rows: 3,
|
||||||
|
resizable: false,
|
||||||
|
placeholder: strings.NoPollMsgPlaceholder,
|
||||||
|
value: this.properties.NoPollMsg
|
||||||
|
}),
|
||||||
|
PropertyFieldChoiceGroupWithCallout('chartType', {
|
||||||
|
calloutContent: React.createElement('div', {}, strings.ChartFieldCalloutText),
|
||||||
|
calloutTrigger: CalloutTriggers.Hover,
|
||||||
|
key: 'choice_charttype',
|
||||||
|
label: strings.ChartFieldLabel,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
key: 'pie',
|
||||||
|
text: 'Pie',
|
||||||
|
checked: this.properties.chartType === ChartType.Pie,
|
||||||
|
iconProps: { officeFabricIconFontName: 'PieSingle' }
|
||||||
|
}, {
|
||||||
|
key: 'doughnut',
|
||||||
|
text: 'Doughnut',
|
||||||
|
checked: this.properties.chartType === ChartType.Doughnut,
|
||||||
|
iconProps: { officeFabricIconFontName: 'DonutChart' }
|
||||||
|
}, {
|
||||||
|
key: 'bar',
|
||||||
|
text: 'Bar',
|
||||||
|
checked: this.properties.chartType === ChartType.Bar,
|
||||||
|
iconProps: { officeFabricIconFontName: 'BarChartVertical' }
|
||||||
|
}, {
|
||||||
|
key: 'horizontalBar',
|
||||||
|
text: 'Horizontal Bar',
|
||||||
|
checked: this.properties.chartType === ChartType.HorizontalBar,
|
||||||
|
iconProps: { officeFabricIconFontName: 'BarChartHorizontal' }
|
||||||
|
}, {
|
||||||
|
key: 'line',
|
||||||
|
text: 'Line',
|
||||||
|
checked: this.properties.chartType === ChartType.Line,
|
||||||
|
iconProps: { officeFabricIconFontName: 'LineChart' }
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ChartControl, ChartType } from '@pnp/spfx-controls-react/lib/ChartControl';
|
||||||
|
import { IPollAnalyticsInfo } from '../../../../Models';
|
||||||
|
|
||||||
|
export interface IQuickPollChartProps {
|
||||||
|
PollAnalytics: IPollAnalyticsInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class QuickPollChart extends React.Component<IQuickPollChartProps, {}> {
|
||||||
|
private charttype: ChartType = null;
|
||||||
|
public render(): React.ReactElement<IQuickPollChartProps> {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="ms-Grid" dir="ltr">
|
||||||
|
<div className="ms-Grid-row">
|
||||||
|
<div className="ms-Grid-col ms-lg12 ms-md12 ms-sm12">
|
||||||
|
<div className="ms-textAlignLeft ms-font-m-plus ms-fontWeight-semibold">
|
||||||
|
{this.props.PollAnalytics ? this.props.PollAnalytics.Question : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ms-Grid-row">
|
||||||
|
<div className="ms-Grid-col ms-lg12 ms-md12 ms-sm12">
|
||||||
|
{this.renderChart()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderChart(): JSX.Element {
|
||||||
|
const { PollAnalytics } = this.props;
|
||||||
|
if (undefined !== this.props.PollAnalytics) {
|
||||||
|
this.getChartType();
|
||||||
|
return (
|
||||||
|
this.charttype == ChartType.Line ? (
|
||||||
|
<ChartControl
|
||||||
|
loadingtemplate={() => <div>Please wait...</div>}
|
||||||
|
type={this.charttype}
|
||||||
|
data={{
|
||||||
|
labels: PollAnalytics.Labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Results',
|
||||||
|
data: PollAnalytics.PollResponse,
|
||||||
|
fill: false,
|
||||||
|
borderColor: "rgb(77, 139, 240)"
|
||||||
|
}]
|
||||||
|
}} />
|
||||||
|
) : (
|
||||||
|
<ChartControl
|
||||||
|
loadingtemplate={() => <div>Please wait...</div>}
|
||||||
|
type={this.charttype}
|
||||||
|
data={{
|
||||||
|
labels: PollAnalytics.Labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Results',
|
||||||
|
data: PollAnalytics.PollResponse,
|
||||||
|
}]
|
||||||
|
}} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getChartType = () => {
|
||||||
|
switch (this.props.PollAnalytics.ChartType.toLocaleLowerCase()) {
|
||||||
|
case 'pie':
|
||||||
|
this.charttype = ChartType.Pie;
|
||||||
|
break;
|
||||||
|
case 'doughnut':
|
||||||
|
this.charttype = ChartType.Doughnut;
|
||||||
|
break;
|
||||||
|
case 'bar':
|
||||||
|
this.charttype = ChartType.Bar;
|
||||||
|
break;
|
||||||
|
case 'horizontalbar':
|
||||||
|
this.charttype = ChartType.HorizontalBar;
|
||||||
|
break;
|
||||||
|
case 'line':
|
||||||
|
this.charttype = ChartType.Line;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.charttype = ChartType.Doughnut;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { IUserInfo } from "../../../Models";
|
||||||
|
import { ChartType } from "@pnp/spfx-controls-react/lib/ChartControl";
|
||||||
|
|
||||||
|
export interface ISimplePollProps {
|
||||||
|
pollQuestions: any[];
|
||||||
|
SuccessfullVoteSubmissionMsg: string;
|
||||||
|
ResponseMsgToUser: string;
|
||||||
|
BtnSubmitVoteText: string;
|
||||||
|
chartType: ChartType;
|
||||||
|
pollBasedOnDate: boolean;
|
||||||
|
currentUserInfo: IUserInfo;
|
||||||
|
NoPollMsg: string;
|
||||||
|
openPropertyPane: () => void;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { IQuestionDetails, IResponseDetails } from "../../../Models";
|
||||||
|
|
||||||
|
export interface ISimplePollState {
|
||||||
|
listExists: boolean;
|
||||||
|
PollQuestions: IQuestionDetails[];
|
||||||
|
UserResponse: IResponseDetails[];
|
||||||
|
displayQuestionId: string;
|
||||||
|
displayQuestion: IQuestionDetails;
|
||||||
|
enableSubmit: boolean;
|
||||||
|
enableChoices: boolean;
|
||||||
|
showOptions: boolean;
|
||||||
|
showProgress: boolean;
|
||||||
|
showChart: boolean;
|
||||||
|
showChartProgress: boolean;
|
||||||
|
showMessage: boolean;
|
||||||
|
isError: boolean;
|
||||||
|
MsgContent: string;
|
||||||
|
PollAnalytics: any; //IPollAnalyticsInfo;
|
||||||
|
showSubmissionProgress: boolean;
|
||||||
|
currentPollResponse: string;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
.MessageContainer{
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold !important;
|
||||||
|
line-height: 1.5em;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
.errorMessage{
|
||||||
|
color: red !important;
|
||||||
|
padding-top: 10px !important;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.successMessage{
|
||||||
|
color: #64BE1A !important;
|
||||||
|
padding-top: 10px !important;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.warningMessage{
|
||||||
|
color: #BEBB1A !important;
|
||||||
|
padding-top: 10px !important;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.infoMessage{
|
||||||
|
background-color: rgb(148, 210, 230) !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||||
|
import { Text } from 'office-ui-fabric-react/lib/Text';
|
||||||
|
import styles from './MessageContainer.module.scss';
|
||||||
|
import { MessageScope } from '../../../../Common/enumHelper';
|
||||||
|
|
||||||
|
export interface IMessageContainerProps {
|
||||||
|
Message?: string;
|
||||||
|
MessageScope: MessageScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MessageContainer(props: IMessageContainerProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles.MessageContainer}>
|
||||||
|
{
|
||||||
|
props.MessageScope === MessageScope.Success &&
|
||||||
|
<MessageBar messageBarType={MessageBarType.success}>
|
||||||
|
<Text block variant={"mediumPlus"}>{props.Message}</Text>
|
||||||
|
</MessageBar>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.MessageScope === MessageScope.Failure &&
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
<Text block variant={"mediumPlus"}>{props.Message}</Text>
|
||||||
|
</MessageBar>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.MessageScope === MessageScope.Warning &&
|
||||||
|
<MessageBar messageBarType={MessageBarType.warning}>
|
||||||
|
<Text block variant={"mediumPlus"}>{props.Message}</Text>
|
||||||
|
</MessageBar>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.MessageScope === MessageScope.Info &&
|
||||||
|
<MessageBar className={styles.infoMessage}>
|
||||||
|
<Text block variant={"mediumPlus"}>{props.Message}</Text>
|
||||||
|
</MessageBar>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
|
||||||
|
export interface IOptionsContainerProps {
|
||||||
|
disabled: boolean;
|
||||||
|
selectedKey?: () => string;
|
||||||
|
options: string;
|
||||||
|
label?: string;
|
||||||
|
multiSelect: boolean;
|
||||||
|
onChange?: (ev: any, option: any, isMultiSel: boolean) => void;
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
|
||||||
|
import { List } from 'office-ui-fabric-react/lib/List';
|
||||||
|
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
|
||||||
|
import { IOptionsContainerProps } from './IOptionsContainerProps';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
export interface IOptionsContainerState {
|
||||||
|
selChoices?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class OptionsContainer extends React.Component<IOptionsContainerProps, IOptionsContainerState> {
|
||||||
|
constructor(props: IOptionsContainerProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
selChoices: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
const { disabled, selectedKey, label, options, onChange, multiSelect } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{multiSelect ? (
|
||||||
|
<div style={{ paddingTop: "15px" }}>
|
||||||
|
<List items={this.getOptions()} onRenderCell={this._onRenderCell} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ChoiceGroup disabled={disabled}
|
||||||
|
selectedKey={this._getSelectedKey()}
|
||||||
|
options={this.onRenderChoiceOptions()} required={true} label=""
|
||||||
|
onChange={this._onChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOptions = (): string[] => {
|
||||||
|
let tempChoices: string[] = [];
|
||||||
|
if (this.props.options.indexOf(',') >= 0) {
|
||||||
|
tempChoices = this.props.options.split(',');
|
||||||
|
} else tempChoices.push(this.props.options);
|
||||||
|
return tempChoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onRenderCell = (item: any, index: number | undefined): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: "15px" }}>
|
||||||
|
<Checkbox label={item} onChange={this._makeChangeHandler(item)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRenderChoiceOptions(): IChoiceGroupOption[] {
|
||||||
|
let choices: IChoiceGroupOption[] = [];
|
||||||
|
let tempChoices: string[] = this.getOptions();
|
||||||
|
if (tempChoices.length > 0) {
|
||||||
|
tempChoices.map((choice: string) => {
|
||||||
|
choices.push({
|
||||||
|
key: choice.trim(),
|
||||||
|
text: choice.trim()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
choices.push({
|
||||||
|
key: '0',
|
||||||
|
text: "Sorry, no choices found",
|
||||||
|
disabled: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getSelectedKey = (): string => {
|
||||||
|
return this.props.selectedKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onChange = (ev: React.FormEvent<HTMLInputElement>, option: any): void => {
|
||||||
|
this.props.onChange(ev, option, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _makeChangeHandler = (item: string) => {
|
||||||
|
return (ev: any, checked: boolean) => this._onCheckboxChange(ev, checked, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onCheckboxChange = (ev: any, isChecked: boolean, item: string) => {
|
||||||
|
let finalSel: string[] = this.state.selChoices;
|
||||||
|
if (finalSel.length > 0) {
|
||||||
|
if (isChecked) {
|
||||||
|
finalSel.push(item);
|
||||||
|
} else finalSel = _.filter(finalSel, (o) => { return o !== item; });
|
||||||
|
} else {
|
||||||
|
if (isChecked) finalSel.push(item);
|
||||||
|
}
|
||||||
|
this.setState({ selChoices: finalSel });
|
||||||
|
this.props.onChange(ev, { key: finalSel }, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||||
|
|
||||||
|
.simplePoll {
|
||||||
|
padding: 10px;
|
||||||
|
.container {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0px auto;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,385 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './SimplePoll.module.scss';
|
||||||
|
import * as strings from 'SimplePollWebPartStrings';
|
||||||
|
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
||||||
|
import { ProgressIndicator } from 'office-ui-fabric-react/lib/ProgressIndicator';
|
||||||
|
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { ISimplePollProps } from './ISimplePollProps';
|
||||||
|
import { ISimplePollState } from './ISimplePollState';
|
||||||
|
import OptionsContainer from './OptionsContainer/OptionsContainer';
|
||||||
|
import MessageContainer from './MessageContainer/MessageContainer';
|
||||||
|
import QuickPollChart from './ChartContainer/QuickPollChart';
|
||||||
|
import { IQuestionDetails, IResponseDetails, IPollAnalyticsInfo } from '../../../Models';
|
||||||
|
import SPHelper from '../../../Common/SPHelper';
|
||||||
|
import { MessageScope } from '../../../Common/enumHelper';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
export default class SimplePoll extends React.Component<ISimplePollProps, ISimplePollState> {
|
||||||
|
private helper: SPHelper = null;
|
||||||
|
private disQuestionId: string;
|
||||||
|
private displayQuestion: IQuestionDetails;
|
||||||
|
constructor(props: ISimplePollProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
listExists: false,
|
||||||
|
PollQuestions: [],
|
||||||
|
UserResponse: [],
|
||||||
|
displayQuestionId: "",
|
||||||
|
displayQuestion: null,
|
||||||
|
enableSubmit: true,
|
||||||
|
enableChoices: true,
|
||||||
|
showOptions: false,
|
||||||
|
showProgress: false,
|
||||||
|
showChart: false,
|
||||||
|
showChartProgress: false,
|
||||||
|
PollAnalytics: undefined,
|
||||||
|
showMessage: false,
|
||||||
|
isError: false,
|
||||||
|
MsgContent: "",
|
||||||
|
showSubmissionProgress: false,
|
||||||
|
currentPollResponse: ""
|
||||||
|
};
|
||||||
|
this.helper = new SPHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount = () => {
|
||||||
|
this.checkAndCreateList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate = (prevProps: ISimplePollProps) => {
|
||||||
|
if (prevProps.pollQuestions !== this.props.pollQuestions || prevProps.pollBasedOnDate !== this.props.pollBasedOnDate) {
|
||||||
|
this.setState({
|
||||||
|
UserResponse: [],
|
||||||
|
displayQuestion: null,
|
||||||
|
displayQuestionId: ''
|
||||||
|
}, () => {
|
||||||
|
this.getQuestions(this.props.pollQuestions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (prevProps.chartType !== this.props.chartType) {
|
||||||
|
let newPollAnalytics: IPollAnalyticsInfo = this.state.PollAnalytics;
|
||||||
|
newPollAnalytics.ChartType = this.props.chartType;
|
||||||
|
this.setState({
|
||||||
|
PollAnalytics: newPollAnalytics
|
||||||
|
}, this.bindResponseAnalytics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkAndCreateList() {
|
||||||
|
this.helper = new SPHelper();
|
||||||
|
let listCreated = await this.helper.checkListExists();
|
||||||
|
if (listCreated) {
|
||||||
|
this.setState({ listExists: true }, () => {
|
||||||
|
this.getQuestions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getQuestions = (questions?: any[]) => {
|
||||||
|
let pquestions: IQuestionDetails[] = [];
|
||||||
|
let tmpQuestions: any[] = (questions) ? questions : (this.props.pollQuestions) ? this.props.pollQuestions : [];
|
||||||
|
if (tmpQuestions && tmpQuestions.length > 0) {
|
||||||
|
tmpQuestions.map((question) => {
|
||||||
|
pquestions.push({
|
||||||
|
Id: question.uniqueId,
|
||||||
|
DisplayName: question.QTitle,
|
||||||
|
Choices: question.QOptions,
|
||||||
|
UseDate: question.QUseDate,
|
||||||
|
StartDate: new Date(question.QStartDate),
|
||||||
|
EndDate: new Date(question.QEndDate),
|
||||||
|
MultiChoice: question.QMultiChoice,
|
||||||
|
SortIdx: question.sortIdx
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.disQuestionId = this.getDisplayQuestionID(pquestions);
|
||||||
|
this.setState({ PollQuestions: pquestions, displayQuestionId: this.disQuestionId, displayQuestion: this.displayQuestion }, this.bindPolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDisplayQuestionID = (questions?: any[]) => {
|
||||||
|
let filQuestions: any[] = [];
|
||||||
|
if (questions.length > 0) {
|
||||||
|
if (this.props.pollBasedOnDate) {
|
||||||
|
filQuestions = _.filter(questions, (o) => { return moment().startOf('date') >= moment(o.StartDate) && moment(o.EndDate) >= moment().startOf('date'); });
|
||||||
|
} else {
|
||||||
|
filQuestions = _.orderBy(questions, ['SortIdx'], ['asc']);
|
||||||
|
this.displayQuestion = filQuestions[0];
|
||||||
|
return filQuestions[0].Id;
|
||||||
|
}
|
||||||
|
if (filQuestions.length > 0) {
|
||||||
|
filQuestions = _.orderBy(filQuestions, ['SortIdx'], ['asc']);
|
||||||
|
this.displayQuestion = filQuestions[0];
|
||||||
|
return filQuestions[0].Id;
|
||||||
|
} else {
|
||||||
|
this.displayQuestion = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindPolls = () => {
|
||||||
|
this.setState({
|
||||||
|
showProgress: (this.state.PollQuestions.length > 0) ? true : false,
|
||||||
|
enableSubmit: true,
|
||||||
|
enableChoices: true,
|
||||||
|
showOptions: false,
|
||||||
|
showChart: false,
|
||||||
|
showChartProgress: false,
|
||||||
|
PollAnalytics: undefined,
|
||||||
|
showMessage: false,
|
||||||
|
isError: false,
|
||||||
|
MsgContent: "",
|
||||||
|
showSubmissionProgress: false
|
||||||
|
}, this.getAllUsersResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onChange = (ev: any, option: any, isMultiSel: boolean): void => {
|
||||||
|
let prevUserResponse = this.state.UserResponse;
|
||||||
|
let userresponse: IResponseDetails;
|
||||||
|
userresponse = {
|
||||||
|
PollQuestionId: this.state.displayQuestion.Id,
|
||||||
|
PollQuestion: this.state.displayQuestion.DisplayName,
|
||||||
|
PollResponse: !isMultiSel ? option.key : '',
|
||||||
|
UserID: this.props.currentUserInfo.ID,
|
||||||
|
UserDisplayName: this.props.currentUserInfo.DisplayName,
|
||||||
|
UserLoginName: this.props.currentUserInfo.LoginName,
|
||||||
|
PollMultiResponse: isMultiSel ? option.key : [],
|
||||||
|
IsMulti: isMultiSel
|
||||||
|
};
|
||||||
|
if (prevUserResponse.length > 0) {
|
||||||
|
let filRes = this.getUserResponse(prevUserResponse);
|
||||||
|
if (filRes.length > 0) {
|
||||||
|
!isMultiSel ? filRes[0].PollResponse = option.key : filRes[0].PollMultiResponse = option.key;
|
||||||
|
} else {
|
||||||
|
prevUserResponse.push(userresponse);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prevUserResponse.push(userresponse);
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
UserResponse: prevUserResponse
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getSelectedKey = (): string => {
|
||||||
|
let selKey: string = "";
|
||||||
|
if (this.state.UserResponse && this.state.UserResponse.length > 0) {
|
||||||
|
var userResponses = this.state.UserResponse;
|
||||||
|
var userRes = this.getUserResponse(userResponses);
|
||||||
|
if (userRes.length > 0) {
|
||||||
|
selKey = userRes[0].PollResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _submitVote = async () => {
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
enableSubmit: false,
|
||||||
|
enableChoices: false,
|
||||||
|
showSubmissionProgress: false,
|
||||||
|
isError: false,
|
||||||
|
MsgContent: '',
|
||||||
|
showMessage: false
|
||||||
|
});
|
||||||
|
var curUserRes = this.getUserResponse(this.state.UserResponse);
|
||||||
|
if (curUserRes.length <= 0) {
|
||||||
|
this.setState({
|
||||||
|
MsgContent: strings.SubmitValidationMessage,
|
||||||
|
isError: true,
|
||||||
|
showMessage: true,
|
||||||
|
enableSubmit: true,
|
||||||
|
enableChoices: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
enableSubmit: false,
|
||||||
|
enableChoices: false,
|
||||||
|
showSubmissionProgress: true,
|
||||||
|
isError: false,
|
||||||
|
MsgContent: '',
|
||||||
|
showMessage: false
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await this.helper.submitResponse(curUserRes[0]);
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
showSubmissionProgress: false,
|
||||||
|
showMessage: true,
|
||||||
|
isError: false,
|
||||||
|
MsgContent: (this.props.SuccessfullVoteSubmissionMsg && this.props.SuccessfullVoteSubmissionMsg.trim()) ?
|
||||||
|
this.props.SuccessfullVoteSubmissionMsg.trim() : strings.SuccessfullVoteSubmission,
|
||||||
|
showChartProgress: true
|
||||||
|
}, this.getAllUsersResponse);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
enableSubmit: true,
|
||||||
|
enableChoices: true,
|
||||||
|
showSubmissionProgress: false,
|
||||||
|
showMessage: true,
|
||||||
|
isError: true,
|
||||||
|
MsgContent: strings.FailedVoteSubmission
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAllUsersResponse = async () => {
|
||||||
|
let usersResponse = await this.helper.getPollResponse((this.state.displayQuestionId) ? this.state.displayQuestionId : this.disQuestionId);
|
||||||
|
var filRes = _.filter(usersResponse, (o) => { return o.UserID == this.props.currentUserInfo.ID; });
|
||||||
|
if (filRes.length > 0) {
|
||||||
|
this.setState({
|
||||||
|
showChartProgress: true,
|
||||||
|
showChart: true,
|
||||||
|
showOptions: false,
|
||||||
|
showProgress: false,
|
||||||
|
UserResponse: usersResponse,
|
||||||
|
currentPollResponse: usersResponse[0].Response ? usersResponse[0].Response : usersResponse[0].MultiResponse.join(',')
|
||||||
|
}, this.bindResponseAnalytics);
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
showProgress: false,
|
||||||
|
showOptions: true,
|
||||||
|
showChartProgress: false,
|
||||||
|
showChart: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindResponseAnalytics = () => {
|
||||||
|
const { PollQuestions, displayQuestion } = this.state;
|
||||||
|
let tmpUserResponse: any = this.state.UserResponse;
|
||||||
|
if (tmpUserResponse && tmpUserResponse.length > 0) {
|
||||||
|
var tempData: any;
|
||||||
|
let qChoices: string[] = displayQuestion.Choices.split(',');
|
||||||
|
var finalData = [];
|
||||||
|
if (!displayQuestion.MultiChoice) {
|
||||||
|
tempData = _.countBy(tmpUserResponse, 'Response');
|
||||||
|
} else {
|
||||||
|
var data = [];
|
||||||
|
tmpUserResponse.map((res: any) => {
|
||||||
|
if (res.MultiResponse && res.MultiResponse.length > 0) {
|
||||||
|
res.MultiResponse.map((finres: any) => {
|
||||||
|
data.push({
|
||||||
|
"UserID": res.UserID,
|
||||||
|
"Response": finres.trim()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tempData = _.countBy(data, 'Response');
|
||||||
|
}
|
||||||
|
qChoices.map((label) => {
|
||||||
|
if (tempData[label.trim()] == undefined) {
|
||||||
|
finalData.push(0);
|
||||||
|
} else finalData.push(tempData[label.trim()]);
|
||||||
|
});
|
||||||
|
var pollAnalytics: IPollAnalyticsInfo;
|
||||||
|
pollAnalytics = {
|
||||||
|
ChartType: this.props.chartType,
|
||||||
|
Labels: qChoices,
|
||||||
|
Question: displayQuestion.DisplayName,
|
||||||
|
PollResponse: finalData
|
||||||
|
};
|
||||||
|
this.setState({
|
||||||
|
showProgress: false,
|
||||||
|
showOptions: false,
|
||||||
|
showChartProgress: false,
|
||||||
|
showChart: true,
|
||||||
|
PollAnalytics: pollAnalytics
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUserResponse(UserResponses: IResponseDetails[]): IResponseDetails[] {
|
||||||
|
let retUserResponse: IResponseDetails[];
|
||||||
|
retUserResponse = UserResponses.filter((res) => { return res.UserID == this.props.currentUserInfo.ID; });
|
||||||
|
return retUserResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): React.ReactElement<ISimplePollProps> {
|
||||||
|
const { pollQuestions, BtnSubmitVoteText, ResponseMsgToUser, NoPollMsg } = this.props;
|
||||||
|
const { showProgress, enableChoices, showSubmissionProgress, showChartProgress, PollQuestions, showMessage, MsgContent, isError,
|
||||||
|
showOptions, showChart, PollAnalytics, currentPollResponse, enableSubmit, listExists, displayQuestion } = this.state;
|
||||||
|
const showConfig: boolean = (!pollQuestions || pollQuestions.length <= 0 && (!PollQuestions || PollQuestions.length <= 0)) ? true : false;
|
||||||
|
let userResponseCaption: string = (ResponseMsgToUser && ResponseMsgToUser.trim()) ? ResponseMsgToUser.trim() : strings.DefaultResponseMsgToUser;
|
||||||
|
let submitButtonText: string = (BtnSubmitVoteText && BtnSubmitVoteText.trim()) ? BtnSubmitVoteText.trim() : strings.BtnSumbitVote;
|
||||||
|
let nopollmsg: string = (NoPollMsg && NoPollMsg.trim()) ? NoPollMsg.trim() : strings.NoPollMsgDefault;
|
||||||
|
return (
|
||||||
|
<div className={styles.simplePoll}>
|
||||||
|
{!listExists ? (
|
||||||
|
<ProgressIndicator label={strings.ListCreationText} description={strings.PlsWait} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{showConfig &&
|
||||||
|
<Placeholder iconName='Edit'
|
||||||
|
iconText={strings.PlaceholderIconText}
|
||||||
|
description={strings.PlaceholderDescription}
|
||||||
|
buttonLabel={strings.PlaceholderButtonLabel}
|
||||||
|
onConfigure={this.props.openPropertyPane} />
|
||||||
|
}
|
||||||
|
{showProgress && !showChart &&
|
||||||
|
<ProgressIndicator label={strings.QuestionLoadingText} description={strings.PlsWait} />
|
||||||
|
}
|
||||||
|
{!displayQuestion && !showConfig &&
|
||||||
|
<MessageContainer MessageScope={MessageScope.Info} Message={nopollmsg} />
|
||||||
|
}
|
||||||
|
{PollQuestions && PollQuestions.length > 0 && showOptions && displayQuestion &&
|
||||||
|
<div className="ms-Grid" dir="ltr">
|
||||||
|
<div className="ms-Grid-row">
|
||||||
|
<div className="ms-Grid-col ms-lg12 ms-md12 ms-sm12">
|
||||||
|
<div className="ms-textAlignLeft ms-font-m-plus ms-fontWeight-semibold">
|
||||||
|
{displayQuestion.DisplayName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ms-Grid-row">
|
||||||
|
<div className="ms-Grid-col ms-lg12 ms-md12 ms-sm12">
|
||||||
|
<div className="ms-textAlignLeft ms-font-m-plus ms-fontWeight-semibold">
|
||||||
|
<OptionsContainer disabled={!enableChoices} multiSelect={displayQuestion.MultiChoice}
|
||||||
|
selectedKey={this._getSelectedKey}
|
||||||
|
options={displayQuestion.Choices}
|
||||||
|
label="Pick One"
|
||||||
|
onChange={this._onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ms-Grid-row">
|
||||||
|
<div className="ms-Grid-col ms-lg12 ms-md12 ms-sm12">
|
||||||
|
<div className="ms-textAlignCenter ms-font-m-plus ms-fontWeight-semibold">
|
||||||
|
<PrimaryButton disabled={!enableSubmit} text={submitButtonText}
|
||||||
|
onClick={this._submitVote.bind(this)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{showSubmissionProgress && !showChartProgress &&
|
||||||
|
<ProgressIndicator label={strings.SubmissionLoadingText} description={strings.PlsWait} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{showMessage && MsgContent &&
|
||||||
|
<MessageContainer MessageScope={(isError) ? MessageScope.Failure : MessageScope.Success} Message={MsgContent} />
|
||||||
|
}
|
||||||
|
{showChartProgress && !showChart &&
|
||||||
|
<ProgressIndicator label="Loading the Poll analytics" description="Getting all the responses..." />
|
||||||
|
}
|
||||||
|
{showChart &&
|
||||||
|
<>
|
||||||
|
<QuickPollChart PollAnalytics={PollAnalytics} />
|
||||||
|
<MessageContainer MessageScope={MessageScope.Info} Message={`${userResponseCaption}: ${currentPollResponse}`} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
PropertyPaneDescription: "Below properties will help you manage your poll questions and extra features that you can leverage.",
|
||||||
|
BasicGroupName: "",
|
||||||
|
ListCreationText: "Verifying the response list...",
|
||||||
|
PlaceholderIconText: "Configure your Quick Poll",
|
||||||
|
PlaceholderDescription: "",
|
||||||
|
PlaceholderButtonLabel: "Come on...",
|
||||||
|
QuestionLoadingText: "Loading Poll data",
|
||||||
|
SubmissionLoadingText: "Submission is inprogress",
|
||||||
|
PlsWait: "Please wait...",
|
||||||
|
PollDateLabel: "Display poll based on date",
|
||||||
|
PollDateCalloutText: "Turning on will display the polls based on the 'Start Date' & 'End Date'",
|
||||||
|
PollQuestionsLabel: "Poll Questions:",
|
||||||
|
PollQuestionsPanelHeader: "Poll Questions list",
|
||||||
|
PollQuestionsManageButton: "Manage Questions Info",
|
||||||
|
MsgAfterSubmissionLabel: "Success Message:",
|
||||||
|
MsgAfterSubmissionDescription: "Message to display after successfull submission. Max of 150 characters are allowed.",
|
||||||
|
MsgAfterSubmissionPlaceholder: "Message...",
|
||||||
|
ResponseMsgToUserLabel: "Response Message:",
|
||||||
|
ResponseMsgToUserDescription: "Message displayed once the user submitted the poll. E.g., 'You voted for: <User Response>'",
|
||||||
|
ResponseMsgToUserPlaceholder: "Message...",
|
||||||
|
DefaultResponseMsgToUser: "You voted for",
|
||||||
|
SuccessfullVoteSubmission: "Thank you for your submission",
|
||||||
|
FailedVoteSubmission: "Sorry, something wrong while submitting. Please try again after sometime.",
|
||||||
|
BtnSumbitVote: "Submit Vote",
|
||||||
|
BtnSumbitVoteLabel: "Submit Button Text:",
|
||||||
|
BtnSumbitVoteDescription: "Text to display on the submit button. Max of 50 characters are allowed",
|
||||||
|
BtnSumbitVotePlaceholder: "Submit button text...",
|
||||||
|
SubmitValidationMessage: "Please select any option!",
|
||||||
|
ChartFieldLabel: "Preferred Chart Type",
|
||||||
|
ChartFieldCalloutText: "Select preferrable chart type",
|
||||||
|
NoPollMsgLabel: "No Poll Message:",
|
||||||
|
NoPollMsgDescription: "Message to be displayed when there is no polls.",
|
||||||
|
NoPollMsgPlaceholder: "Message...",
|
||||||
|
NoPollMsgDefault: "Sorry, no polls to display!",
|
||||||
|
|
||||||
|
Q_Title_Title: "Question Title",
|
||||||
|
Q_Title_Placeholder: "Question Title...",
|
||||||
|
Q_Options_Title: "Choices",
|
||||||
|
Q_Options_Placeholder: "Choices separated by comma",
|
||||||
|
MultiChoice_Title: "Multi Selection",
|
||||||
|
Q_StartDate_Title: "Start Date",
|
||||||
|
Q_EndDate_Title: "End Date"
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,49 @@
|
||||||
|
declare interface ISimplePollWebPartStrings {
|
||||||
|
PropertyPaneDescription: string;
|
||||||
|
BasicGroupName: string;
|
||||||
|
ListCreationText: string;
|
||||||
|
PlaceholderIconText: string;
|
||||||
|
PlaceholderDescription: string;
|
||||||
|
PlaceholderButtonLabel: string;
|
||||||
|
QuestionLoadingText: string;
|
||||||
|
SubmissionLoadingText: string;
|
||||||
|
PlsWait: string;
|
||||||
|
PollDateLabel: string;
|
||||||
|
PollDateCalloutText: string;
|
||||||
|
PollQuestionsLabel: string;
|
||||||
|
PollQuestionsPanelHeader: string;
|
||||||
|
PollQuestionsManageButton: string;
|
||||||
|
MsgAfterSubmissionLabel: string;
|
||||||
|
MsgAfterSubmissionDescription: string;
|
||||||
|
MsgAfterSubmissionPlaceholder: string;
|
||||||
|
ResponseMsgToUserLabel: string;
|
||||||
|
ResponseMsgToUserDescription: string;
|
||||||
|
ResponseMsgToUserPlaceholder: string;
|
||||||
|
DefaultResponseMsgToUser: string;
|
||||||
|
SuccessfullVoteSubmission: string;
|
||||||
|
FailedVoteSubmission: string;
|
||||||
|
BtnSumbitVote: string;
|
||||||
|
BtnSumbitVoteLabel: string;
|
||||||
|
BtnSumbitVoteDescription: string;
|
||||||
|
BtnSumbitVotePlaceholder: string;
|
||||||
|
SubmitValidationMessage: string;
|
||||||
|
ChartFieldLabel: string;
|
||||||
|
ChartFieldCalloutText:string;
|
||||||
|
NoPollMsgLabel: string;
|
||||||
|
NoPollMsgDescription: string;
|
||||||
|
NoPollMsgPlaceholder: string;
|
||||||
|
NoPollMsgDefault: string;
|
||||||
|
|
||||||
|
Q_Title_Title: string;
|
||||||
|
Q_Title_Placeholder: string;
|
||||||
|
Q_Options_Title: string;
|
||||||
|
Q_Options_Placeholder: string;
|
||||||
|
MultiChoice_Title: string;
|
||||||
|
Q_StartDate_Title: string;
|
||||||
|
Q_EndDate_Title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'SimplePollWebPartStrings' {
|
||||||
|
const strings: ISimplePollWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "lib",
|
||||||
|
"inlineSources": false,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types",
|
||||||
|
"./node_modules/@microsoft"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"es6-promise",
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"es5",
|
||||||
|
"dom",
|
||||||
|
"es2015.collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||||
|
"rules": {
|
||||||
|
"class-name": false,
|
||||||
|
"export-name": false,
|
||||||
|
"forin": false,
|
||||||
|
"label-position": false,
|
||||||
|
"member-access": true,
|
||||||
|
"no-arg": false,
|
||||||
|
"no-console": false,
|
||||||
|
"no-construct": false,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-eval": false,
|
||||||
|
"no-function-expression": true,
|
||||||
|
"no-internal-module": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-unnecessary-semicolons": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"no-with-statement": true,
|
||||||
|
"semicolon": true,
|
||||||
|
"trailing-comma": false,
|
||||||
|
"typedef": false,
|
||||||
|
"typedef-whitespace": false,
|
||||||
|
"use-named-parameter": true,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue