Merge pull request #1178 from agtenr/dev
Thanks @agtenr this is a very timely sample and it looks great. Would you be interested in presenting this sample at an upcoming SPFx community call? We have an opening for April 9th if you're interested.
This commit is contained in:
commit
c0ad20c740
|
@ -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": "spfx-covid-19-info",
|
||||||
|
"libraryId": "8e6e959a-f902-4bb8-b1ff-ad7e45126607",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Robin Agten
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,77 @@
|
||||||
|
# COVID 19 information web part
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This web part displays info about the COVID-19 virus for a given country.
|
||||||
|
The following info is displayed:
|
||||||
|
- Confirmed cases
|
||||||
|
- Deaths
|
||||||
|
- Recoverd
|
||||||
|
|
||||||
|
![COVID-19 info](./assets/covid-counter.gif)
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
|
||||||
|
![drop](https://img.shields.io/badge/version-1.10.0-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)
|
||||||
|
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-covid19-info | [Robin Agten](https://twitter.com/AgtenRobin)
|
||||||
|
|
||||||
|
## Web part properties
|
||||||
|
|
||||||
|
| Property | Group | Description | Default |
|
||||||
|
|-------------------------------------- |------------------------ |----------------------------------------------------------------------------------------------------------- |--------- |
|
||||||
|
| iso2 Country Code | Country Settings | Defines the country for which the COVID-19 info should be displayed example: BE for Belgium | None |
|
||||||
|
| Show history button | Web part configuration | Determines whether or not the history icon is shown. This can be used to show an graph of historical data | False |
|
||||||
|
| View more statistics | Web part configuration | Provide an optional external link to more details statistics | None |
|
||||||
|
| Up count duration | Web part configuration | Number of seconds for the counters to count up | 2 |
|
||||||
|
| Color for the Confirmed Cases number | Web part configuration | Defines the color of the Confirmed cases number | #69797e |
|
||||||
|
| Color for the Deaths number | Web part configuration | Defines the color of the Deaths number | #d13438 |
|
||||||
|
| Color for the Recovered number | Web part configuration | Defines the color of the Recovered number | #498205 |
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|March 25, 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 serve`
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Configurable Country
|
||||||
|
* Configurable Colors
|
||||||
|
* Optional historical graph
|
||||||
|
* Optional 'view more statistics' link
|
||||||
|
|
||||||
|
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||||
|
|
||||||
|
* Using external APIs using httpClient
|
||||||
|
* [Office Fabric UI REact](https://developer.microsoft.com/en-us/fabric#/)
|
||||||
|
* [SPFx Controls React](https://sharepoint.github.io/sp-dev-fx-controls-react/)
|
||||||
|
* [SPFx Property Controls](https://sharepoint.github.io/sp-dev-fx-property-controls/)
|
||||||
|
* [Recharts](http://recharts.org/en-US/)
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-covid19-info" />
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 846 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"covid-19-info-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/covid19Info/Covid19InfoWebPart.js",
|
||||||
|
"manifest": "./src/webparts/covid19Info/Covid19InfoWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"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": "spfx-covid-19-info",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "spfx-covid-19-info-client-side-solution",
|
||||||
|
"id": "8e6e959a-f902-4bb8-b1ff-ad7e45126607",
|
||||||
|
"version": "1.0.0.0",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"isDomainIsolated": false
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/spfx-covid-19-info.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,50 @@
|
||||||
|
{
|
||||||
|
"name": "spfx-covid-19-info",
|
||||||
|
"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/spfx-controls-react": "1.16.0",
|
||||||
|
"@pnp/spfx-property-controls": "1.17.0",
|
||||||
|
"@types/es6-promise": "0.0.33",
|
||||||
|
"@types/react": "16.8.8",
|
||||||
|
"@types/react-dom": "16.8.3",
|
||||||
|
"@types/webpack-env": "1.13.1",
|
||||||
|
"chart.js": "^2.9.3",
|
||||||
|
"chartist": "^0.11.4",
|
||||||
|
"office-ui-fabric-react": "6.189.2",
|
||||||
|
"react": "16.8.5",
|
||||||
|
"react-chartist": "^0.14.3",
|
||||||
|
"react-chartkick": "^0.4.0",
|
||||||
|
"react-countup": "^4.3.3",
|
||||||
|
"react-dom": "16.8.5",
|
||||||
|
"recharts": "^1.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 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,17 @@
|
||||||
|
export interface ICoronaInfo {
|
||||||
|
countryregion: string;
|
||||||
|
countrycode: ICountryCode;
|
||||||
|
lastupdate: string;
|
||||||
|
location: ILocation;
|
||||||
|
confirmed: number;
|
||||||
|
deaths: number;
|
||||||
|
recovered: number;
|
||||||
|
}
|
||||||
|
export interface ILocation {
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
}
|
||||||
|
export interface ICountryCode {
|
||||||
|
iso2: string;
|
||||||
|
iso3: string;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { IDictionary } from "./IDictionary";
|
||||||
|
|
||||||
|
export interface ICoronaInfoHistory {
|
||||||
|
countryregion: string;
|
||||||
|
lastupdate: string;
|
||||||
|
timeseries: IDictionary<TimeInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeInfo {
|
||||||
|
confirmed: number;
|
||||||
|
deaths: number;
|
||||||
|
recovered: number;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface IDictionary<T> {
|
||||||
|
[Key: string]: T;
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { ICoronaService } from "./ICoronaService";
|
||||||
|
import { HttpClient } from "@microsoft/sp-http";
|
||||||
|
import { ICoronaInfo } from "../models/ICoronaInfo";
|
||||||
|
import { ICoronaInfoHistory } from "../models/ICoronaInfoHistory";
|
||||||
|
|
||||||
|
export class CoronaService implements ICoronaService {
|
||||||
|
|
||||||
|
private httpClient: HttpClient;
|
||||||
|
|
||||||
|
constructor(httpClient: HttpClient) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getCountryInfo(countryCode: string): Promise<ICoronaInfo> {
|
||||||
|
const response = await this.httpClient.get(
|
||||||
|
`https://wuhan-coronavirus-api.laeyoung.endpoint.ainize.ai/jhu-edu/latest?iso2=${countryCode}&onlyCountries=true`,
|
||||||
|
HttpClient.configurations.v1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
console.log(error);
|
||||||
|
throw Error(`Error while fetching the data for country with code '${countryCode}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: ICoronaInfo[] = await response.json();
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
return result[0];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getCountryHistory(countryCode: string): Promise<ICoronaInfoHistory> {
|
||||||
|
const response = await this.httpClient.get(
|
||||||
|
`https://wuhan-coronavirus-api.laeyoung.endpoint.ainize.ai/jhu-edu/timeseries?iso2=${countryCode}&onlyCountries=true`,
|
||||||
|
HttpClient.configurations.v1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
console.log(error);
|
||||||
|
throw Error(`Error while fetching the data for country with code '${countryCode}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: ICoronaInfoHistory[] = await response.json();
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
return result[0];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { ICoronaInfo } from "../models/ICoronaInfo";
|
||||||
|
import { ICoronaInfoHistory } from "../models/ICoronaInfoHistory";
|
||||||
|
|
||||||
|
export interface ICoronaService {
|
||||||
|
getCountryInfo(countryCode: string): Promise<ICoronaInfo>;
|
||||||
|
getCountryHistory(countryCode: string): Promise<ICoronaInfoHistory>;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "6f91d98b-45ec-4915-a409-0d789259c5c7",
|
||||||
|
"alias": "Covid19InfoWebPart",
|
||||||
|
"componentType": "WebPart",
|
||||||
|
|
||||||
|
// The "*" signifies that the version should be taken from the package.json
|
||||||
|
"version": "*",
|
||||||
|
"manifestVersion": 2,
|
||||||
|
|
||||||
|
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||||
|
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||||
|
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||||
|
"requiresCustomScript": false,
|
||||||
|
"supportedHosts": ["SharePointWebPart"],
|
||||||
|
|
||||||
|
"preconfiguredEntries": [{
|
||||||
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||||
|
"group": { "default": "Other" },
|
||||||
|
"title": { "default": "COVID-19 Info" },
|
||||||
|
"description": { "default": "Display regional COVID-19 information" },
|
||||||
|
"officeFabricIconFontName": "Page",
|
||||||
|
"properties": {
|
||||||
|
"description": "COVID 19 Info"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDom from 'react-dom';
|
||||||
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
|
import {
|
||||||
|
IPropertyPaneConfiguration,
|
||||||
|
PropertyPaneTextField,
|
||||||
|
PropertyPaneSlider,
|
||||||
|
PropertyPaneToggle
|
||||||
|
} from '@microsoft/sp-property-pane';
|
||||||
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
|
import Covid19Info from './components/Covid19Info';
|
||||||
|
import { ICovid19InfoProps } from './components/ICovid19InfoProps';
|
||||||
|
import { PropertyFieldColorPicker, PropertyFieldColorPickerStyle } from '@pnp/spfx-property-controls/lib/PropertyFieldColorPicker';
|
||||||
|
|
||||||
|
|
||||||
|
export interface ICovid19InfoWebPartProps {
|
||||||
|
countryCode: string;
|
||||||
|
showHistory: boolean;
|
||||||
|
viewMoreLink: string;
|
||||||
|
countUpTime: number;
|
||||||
|
confirmedColor: string;
|
||||||
|
deathColor: string;
|
||||||
|
recoveredColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Covid19InfoWebPart extends BaseClientSideWebPart <ICovid19InfoWebPartProps> {
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
const element: React.ReactElement<ICovid19InfoProps> = React.createElement(
|
||||||
|
Covid19Info,
|
||||||
|
{
|
||||||
|
countryCode: this.properties.countryCode,
|
||||||
|
showHistory: this.properties.showHistory,
|
||||||
|
viewMoreLink: this.properties.viewMoreLink,
|
||||||
|
countUpTime: this.properties.countUpTime,
|
||||||
|
confirmedColor: this.properties.confirmedColor,
|
||||||
|
deathColor: this.properties.deathColor,
|
||||||
|
recoveredColor: this.properties.recoveredColor,
|
||||||
|
displayMode: this.displayMode,
|
||||||
|
httpClient: this.context.httpClient,
|
||||||
|
onConfigure: this._onConfigure
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: "Display regional COVID-19 information"
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: "Country Settings",
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('countryCode', {
|
||||||
|
label: "iso2 Country Code (e.g. US)"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupName: "Web part configuration",
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneToggle('showHistory', {
|
||||||
|
label: "Show 'View history' button"
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('viewMoreLink', {
|
||||||
|
label: "Provide an optional link to view more statistics"
|
||||||
|
}),
|
||||||
|
PropertyPaneSlider('countUpTime', {
|
||||||
|
label: "Number of second for the count up counters",
|
||||||
|
min: 1,
|
||||||
|
max: 20,
|
||||||
|
value: 2,
|
||||||
|
showValue: true,
|
||||||
|
step:1
|
||||||
|
}),
|
||||||
|
PropertyFieldColorPicker('confirmedColor', {
|
||||||
|
label: 'Color for the Confirmed Cases number',
|
||||||
|
selectedColor: this.properties.confirmedColor,
|
||||||
|
onPropertyChange: this.onPropertyPaneFieldChanged,
|
||||||
|
properties: this.properties,
|
||||||
|
disabled: false,
|
||||||
|
isHidden: false,
|
||||||
|
alphaSliderHidden: true,
|
||||||
|
style: PropertyFieldColorPickerStyle.Inline,
|
||||||
|
iconName: 'Precipitation',
|
||||||
|
key: 'confirmedColorFieldId',
|
||||||
|
}),
|
||||||
|
PropertyFieldColorPicker('deathColor', {
|
||||||
|
label: 'Color for the Deaths number',
|
||||||
|
selectedColor: this.properties.deathColor,
|
||||||
|
onPropertyChange: this.onPropertyPaneFieldChanged,
|
||||||
|
properties: this.properties,
|
||||||
|
disabled: false,
|
||||||
|
isHidden: false,
|
||||||
|
alphaSliderHidden: true,
|
||||||
|
style: PropertyFieldColorPickerStyle.Inline,
|
||||||
|
iconName: 'Precipitation',
|
||||||
|
key: 'deathColorFieldId',
|
||||||
|
}),
|
||||||
|
PropertyFieldColorPicker('recoveredColor', {
|
||||||
|
label: 'Color for the Recovered number',
|
||||||
|
selectedColor: this.properties.recoveredColor,
|
||||||
|
onPropertyChange: this.onPropertyPaneFieldChanged,
|
||||||
|
properties: this.properties,
|
||||||
|
disabled: false,
|
||||||
|
isHidden: false,
|
||||||
|
alphaSliderHidden: true,
|
||||||
|
style: PropertyFieldColorPickerStyle.Inline,
|
||||||
|
iconName: 'Precipitation',
|
||||||
|
key: 'recoveredColorFieldId',
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onConfigure = () => {
|
||||||
|
this.context.propertyPane.open();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||||
|
|
||||||
|
.covid19Info {
|
||||||
|
.refreshIcon {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.historyIcon{
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
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);
|
||||||
|
|
||||||
|
.countryRow{
|
||||||
|
@include ms-Grid-row;
|
||||||
|
.country {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg12;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
@include ms-font-xl;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsRow{
|
||||||
|
@include ms-Grid-row;
|
||||||
|
padding: 10px;
|
||||||
|
.infoColumn {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-md12;
|
||||||
|
@include ms-lg4;
|
||||||
|
text-align: center;
|
||||||
|
.label {
|
||||||
|
width: 100%;
|
||||||
|
@include ms-font-l;
|
||||||
|
}
|
||||||
|
.number {
|
||||||
|
padding: 15px;
|
||||||
|
@include ms-font-xxl;
|
||||||
|
}
|
||||||
|
.confirmed {
|
||||||
|
color: #69797e;
|
||||||
|
}
|
||||||
|
.deaths {
|
||||||
|
color: #d13438;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recovered {
|
||||||
|
color: #498205;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lastUpdatedRow {
|
||||||
|
@include ms-Grid-row;
|
||||||
|
.lastUpdated {
|
||||||
|
@include ms-Grid-col;
|
||||||
|
@include ms-lg12;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
@include ms-font-s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Covid19Info.module.scss';
|
||||||
|
import { ICovid19InfoProps } from './ICovid19InfoProps';
|
||||||
|
import { ICovid19InfoState } from './ICovid19InfoState';
|
||||||
|
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||||
|
import { Placeholder } from '@pnp/spfx-controls-react/lib/Placeholder';
|
||||||
|
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||||
|
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||||
|
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||||
|
import { IconButton } from 'office-ui-fabric-react/lib/Button';
|
||||||
|
import { Link } from 'office-ui-fabric-react/lib/Link';
|
||||||
|
import { Modal, IModalStyleProps, IModalStyles } from 'office-ui-fabric-react/lib/Modal';
|
||||||
|
import { mergeStyleSets, IStyle } from "office-ui-fabric-react/lib/Styling";
|
||||||
|
import { ICoronaService } from '../../../services/ICoronaService';
|
||||||
|
import { CoronaService } from '../../../services/CoronaService';
|
||||||
|
import { HistoryModal } from "./HistoryModal/HistoryModal";
|
||||||
|
import CountUp from "react-countup";
|
||||||
|
import { ICoronaInfoHistory } from '../../../models/ICoronaInfoHistory';
|
||||||
|
import { IStyleFunctionOrObject } from 'office-ui-fabric-react/lib/Utilities';
|
||||||
|
|
||||||
|
export default class Covid19Info extends React.Component<ICovid19InfoProps, ICovid19InfoState> {
|
||||||
|
|
||||||
|
private coronaService: ICoronaService;
|
||||||
|
|
||||||
|
constructor(props: ICovid19InfoProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.coronaService = new CoronaService(props.httpClient);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isLoading: true,
|
||||||
|
coronaInfo: undefined,
|
||||||
|
globalError: undefined,
|
||||||
|
showHistoryModal: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
this._loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps: ICovid19InfoProps) {
|
||||||
|
if (prevProps.countryCode !== this.props.countryCode) {
|
||||||
|
this._loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): React.ReactElement<ICovid19InfoProps> {
|
||||||
|
|
||||||
|
// No country code specified
|
||||||
|
if (!this.props.countryCode) {
|
||||||
|
if (this.props.displayMode === DisplayMode.Read) {
|
||||||
|
return this._renderNoCountryCode();
|
||||||
|
} else {
|
||||||
|
return this._renderPlaceHolder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is loading
|
||||||
|
if (this.state.isLoading) {
|
||||||
|
return this._renderSpinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global Error
|
||||||
|
if (this.state.globalError) {
|
||||||
|
return this._renderError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// No data found
|
||||||
|
if (this.state.coronaInfo === null) {
|
||||||
|
return this._renderNoData();
|
||||||
|
}
|
||||||
|
const confirmedColor: string = this.props.confirmedColor
|
||||||
|
? this.props.confirmedColor
|
||||||
|
: "#69797e";
|
||||||
|
|
||||||
|
const deathColor: string = this.props.deathColor
|
||||||
|
? this.props.deathColor
|
||||||
|
: "#d13438";
|
||||||
|
|
||||||
|
const recoveredColor: string = this.props.recoveredColor
|
||||||
|
? this.props.recoveredColor
|
||||||
|
: "#498205";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.covid19Info}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
{this.props.showHistory && (
|
||||||
|
<IconButton
|
||||||
|
className={styles.historyIcon}
|
||||||
|
iconProps={{iconName: "Chart"}}
|
||||||
|
label={"View history"}
|
||||||
|
onClick={() => this._showHistoryModal()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
className={styles.refreshIcon}
|
||||||
|
iconProps={{iconName: "Refresh"}}
|
||||||
|
label={"Refresh data"}
|
||||||
|
onClick={() => this._loadData()}
|
||||||
|
/>
|
||||||
|
<div className={styles.countryRow}>
|
||||||
|
<div className={styles.country}>
|
||||||
|
<Icon className={styles.icon} iconName={"Globe"}/>
|
||||||
|
<span className={styles.text}>{this.state.coronaInfo.countryregion}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.detailsRow}>
|
||||||
|
<div className={styles.infoColumn}>
|
||||||
|
<div className={styles.label}>{"Confirmed Cases"}</div>
|
||||||
|
<div className={styles.number} style={{ color: confirmedColor}}>
|
||||||
|
<CountUp
|
||||||
|
end={this.state.coronaInfo.confirmed}
|
||||||
|
duration={this.props.countUpTime}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.infoColumn}>
|
||||||
|
<div className={styles.label}>{"Deaths"}</div>
|
||||||
|
<div className={styles.number} style={{ color: deathColor}}>
|
||||||
|
<CountUp
|
||||||
|
end={this.state.coronaInfo.deaths}
|
||||||
|
duration={this.props.countUpTime}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.infoColumn}>
|
||||||
|
<div className={styles.label}>{"Recovered"}</div>
|
||||||
|
<div className={styles.number} style={{ color: recoveredColor}}>
|
||||||
|
<CountUp
|
||||||
|
end={this.state.coronaInfo.recovered}
|
||||||
|
duration={this.props.countUpTime}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.lastUpdatedRow}>
|
||||||
|
<div className={styles.lastUpdated}>
|
||||||
|
{`Last updated:
|
||||||
|
${new Date(this.state.coronaInfo.lastupdate).toLocaleDateString()}
|
||||||
|
${new Date(this.state.coronaInfo.lastupdate).toLocaleTimeString()}`
|
||||||
|
}
|
||||||
|
{this.props.viewMoreLink &&
|
||||||
|
<span> - <Link target={"_blank"} href={this.props.viewMoreLink}>View more statistics</Link></span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
isOpen={this.state.showHistoryModal}
|
||||||
|
onDismiss={this._closeHistoryModal}
|
||||||
|
isBlocking={false}
|
||||||
|
styles={this._getModalStyles()}
|
||||||
|
>
|
||||||
|
|
||||||
|
<HistoryModal
|
||||||
|
countryCode={this.props.countryCode}
|
||||||
|
confirmedColor={confirmedColor}
|
||||||
|
deathColor={deathColor}
|
||||||
|
recoveredColor={recoveredColor}
|
||||||
|
_loadHistoryData={this._loadHistoryData}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadData = async (): Promise<void> => {
|
||||||
|
this.setState({isLoading: true});
|
||||||
|
try {
|
||||||
|
const coronaInfo = await this.coronaService.getCountryInfo(this.props.countryCode);
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
coronaInfo
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({
|
||||||
|
coronaInfo: undefined,
|
||||||
|
isLoading: false,
|
||||||
|
globalError: (error as Error).message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private _loadHistoryData = async (): Promise<ICoronaInfoHistory> => {
|
||||||
|
return await this.coronaService.getCountryHistory(this.props.countryCode);
|
||||||
|
}
|
||||||
|
private _showHistoryModal = (): void => {
|
||||||
|
this.setState({
|
||||||
|
showHistoryModal: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private _closeHistoryModal = (): void => {
|
||||||
|
this.setState({
|
||||||
|
showHistoryModal: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderNoCountryCode = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<MessageBar messageBarType={MessageBarType.warning}>
|
||||||
|
{"Please provide a country code in the web part properties!"}
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _renderPlaceHolder = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Placeholder
|
||||||
|
iconName='Edit'
|
||||||
|
iconText='Configure your web part'
|
||||||
|
description='Please provide a country code in the web part properties'
|
||||||
|
buttonLabel='Configure'
|
||||||
|
onConfigure={this.props.onConfigure} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _renderSpinner = () => {
|
||||||
|
return (
|
||||||
|
<Spinner
|
||||||
|
label={"Loading COVID-19 data...."}
|
||||||
|
size={SpinnerSize.medium}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _renderError = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
{this.state.globalError}
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _renderNoData = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<MessageBar messageBarType={MessageBarType.info}>
|
||||||
|
{`No COVID-19 data could be found for country code: '${this.props.countryCode}'`}
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getModalStyles = (): IStyleFunctionOrObject<IModalStyleProps, IModalStyles> => {
|
||||||
|
const modalStyles: IStyleFunctionOrObject<IModalStyleProps, IModalStyles> = {
|
||||||
|
main: {
|
||||||
|
width: "750px",
|
||||||
|
height: "500px",
|
||||||
|
padding: "15px"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return modalStyles;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||||
|
|
||||||
|
.historyModalContainer {
|
||||||
|
.countryRow{
|
||||||
|
.country {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
@include ms-font-xl;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pivotChart{
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './HistoryModal.module.scss';
|
||||||
|
import { IHistoryModalProps } from "./IHistoryModalProps";
|
||||||
|
import { IHistoryModalState } from "./IHistoryModalState";
|
||||||
|
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||||
|
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||||
|
import { ICoronaInfoHistory } from '../../../../models/ICoronaInfoHistory';
|
||||||
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';
|
||||||
|
import { Pivot, PivotItem } from 'office-ui-fabric-react/lib/Pivot';
|
||||||
|
import { IDataPoint } from "./IDataPoint";
|
||||||
|
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||||
|
|
||||||
|
export class HistoryModal extends React.Component<IHistoryModalProps, IHistoryModalState> {
|
||||||
|
|
||||||
|
constructor(props: IHistoryModalProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isLoading: true,
|
||||||
|
globalError: undefined,
|
||||||
|
historyData: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
this._loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): React.ReactElement<IHistoryModalProps> {
|
||||||
|
|
||||||
|
// Is loading
|
||||||
|
if (this.state.isLoading) {
|
||||||
|
return this._renderSpinner();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global Error
|
||||||
|
if (this.state.globalError) {
|
||||||
|
return this._renderError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// No data found
|
||||||
|
if (this.state.historyData === null) {
|
||||||
|
return this._renderNoData();
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedData = this._mapHistoryData(this.state.historyData);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.historyModalContainer}>
|
||||||
|
<div className={styles.countryRow}>
|
||||||
|
<div className={styles.country}>
|
||||||
|
<Icon className={styles.icon} iconName={"Globe"}/>
|
||||||
|
<span className={styles.text}>{this.state.historyData.countryregion}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Pivot>
|
||||||
|
<PivotItem className={styles.pivotChart} headerText={"Confirmed cases"}>
|
||||||
|
{this._renderChart(mappedData, "confirmed", this.props.confirmedColor, false)}
|
||||||
|
</PivotItem>
|
||||||
|
<PivotItem headerText={"Deaths"}>
|
||||||
|
{this._renderChart(mappedData, "deaths", this.props.deathColor, false)}
|
||||||
|
</PivotItem>
|
||||||
|
<PivotItem headerText={"Recovered"}>
|
||||||
|
{this._renderChart(mappedData, "recovered", this.props.recoveredColor, false)}
|
||||||
|
</PivotItem>
|
||||||
|
<PivotItem headerText={"All"}>
|
||||||
|
{this._renderChart(mappedData, null, null, true)}
|
||||||
|
</PivotItem>
|
||||||
|
</Pivot>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadData = async (): Promise<void> => {
|
||||||
|
this.setState({isLoading: true});
|
||||||
|
try {
|
||||||
|
const historyData: ICoronaInfoHistory = await this.props._loadHistoryData();
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
historyData
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({
|
||||||
|
historyData: undefined,
|
||||||
|
isLoading: false,
|
||||||
|
globalError: (error as Error).message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _mapHistoryData = (historyData: ICoronaInfoHistory): IDataPoint[] => {
|
||||||
|
const dataPoints: IDataPoint[] = new Array();
|
||||||
|
|
||||||
|
for(let key in historyData.timeseries) {
|
||||||
|
dataPoints.push({
|
||||||
|
date: key,
|
||||||
|
confirmed: historyData.timeseries[key].confirmed,
|
||||||
|
deaths: historyData.timeseries[key].deaths,
|
||||||
|
recovered: historyData.timeseries[key].recovered
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderSpinner = () => {
|
||||||
|
return (
|
||||||
|
<Spinner
|
||||||
|
label={"Loading COVID-19 data...."}
|
||||||
|
size={SpinnerSize.medium}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _renderError = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
{this.state.globalError}
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _renderNoData = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<MessageBar messageBarType={MessageBarType.info}>
|
||||||
|
{`No historical data could be found for country code: '${this.props.countryCode}'`}
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _renderChart = (data: IDataPoint[], dataKey: string, color: string, showAll: boolean): JSX.Element => {
|
||||||
|
if (showAll) {
|
||||||
|
return (
|
||||||
|
<LineChart
|
||||||
|
width={700}
|
||||||
|
height={300}
|
||||||
|
data={data}
|
||||||
|
margin={{
|
||||||
|
top: 5, right: 30, left: 20, bottom: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid />
|
||||||
|
<XAxis dataKey="date" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip />
|
||||||
|
<Legend />
|
||||||
|
<Line type={"monotone"} dataKey={"confirmed"} stroke={this.props.confirmedColor} />
|
||||||
|
<Line type={"monotone"} dataKey={"deaths"} stroke={this.props.deathColor} />
|
||||||
|
<Line type={"monotone"} dataKey={"recovered"} stroke={this.props.recoveredColor} />
|
||||||
|
</LineChart>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<LineChart
|
||||||
|
width={700}
|
||||||
|
height={300}
|
||||||
|
data={data}
|
||||||
|
>
|
||||||
|
<CartesianGrid />
|
||||||
|
<XAxis dataKey="date" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip />
|
||||||
|
<Line type={"monotone"} dataKey={dataKey} stroke={color} />
|
||||||
|
</LineChart>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IDataPoint {
|
||||||
|
date: string;
|
||||||
|
confirmed: number;
|
||||||
|
deaths: number;
|
||||||
|
recovered: number;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { ICoronaInfoHistory } from "../../../../models/ICoronaInfoHistory";
|
||||||
|
|
||||||
|
export interface IHistoryModalProps {
|
||||||
|
countryCode: string;
|
||||||
|
confirmedColor: string;
|
||||||
|
deathColor: string;
|
||||||
|
recoveredColor: string;
|
||||||
|
_loadHistoryData(): Promise<ICoronaInfoHistory>;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { ICoronaInfoHistory } from "../../../../models/ICoronaInfoHistory";
|
||||||
|
|
||||||
|
export interface IHistoryModalState {
|
||||||
|
isLoading: boolean;
|
||||||
|
historyData: ICoronaInfoHistory;
|
||||||
|
globalError: string;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||||
|
import { HttpClient } from "@microsoft/sp-http";
|
||||||
|
|
||||||
|
export interface ICovid19InfoProps {
|
||||||
|
countryCode: string;
|
||||||
|
showHistory: boolean;
|
||||||
|
viewMoreLink: string;
|
||||||
|
countUpTime: number;
|
||||||
|
confirmedColor: string;
|
||||||
|
deathColor: string;
|
||||||
|
recoveredColor: string;
|
||||||
|
displayMode: DisplayMode;
|
||||||
|
httpClient: HttpClient;
|
||||||
|
onConfigure(): void;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { ICoronaInfo } from "../../../models/ICoronaInfo";
|
||||||
|
|
||||||
|
export interface ICovid19InfoState {
|
||||||
|
isLoading: boolean;
|
||||||
|
coronaInfo: ICoronaInfo;
|
||||||
|
globalError: string;
|
||||||
|
showHistoryModal: boolean;
|
||||||
|
}
|
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