Introduction
As of late, I have gone through the Aarogy Setu public API which is provided by the Indian government to check the availability of the vaccine slots by Pincode number or by districts in a particular state. By checking this API, I was thinking that many people use MS Team in a number of organizations, and they are looking for a vaccination slot every day by logging in into the Cowin website. So, I started to develop code using SPFx for Ms team Tab and user can easily check vaccination slot available or on the area which is searched by the user.
Today we are accessing Setu API from SPFx solution using SPFx inbuilt library to access any external APIs and get records based on the selected Pincode or district records. Records will be displayed in a proper manner using Fluent UI and Material UI controls.
Once the solution is developed we will make some configurations to make accessing this solution available from Microsoft Teams APP.
I am not including the process to create a new SPFx solution. There are plenty of articles and blogs available to get started SPFx solution.
If you are new to making an SPFx solution then follow
this link to get started.
API Information
We have implemented the below API in this sample code.
Step 1
I am assuming that you have created your SPFx Solution and you have solution structure as below.
I am using the below name for solution artifacts.
- Solution Name: CheckVaccineSlots
-
WebPart Name: VaccineSlotsWebpart
-
Primary Component: VaccineSlots
Step 2
Install the below required packages to solution.
-
- npm i --save @devexpress/dx-react-core @devexpress/dx-react-grid
-
- npm install @material-ui/core
-
- npm i @material-ui/icons/ChevronLeft
-
- npm i @fluentui/react
-
- npm i @pnp/spfx-controls-react
-
- npm i moment
Step 3
Create a service file to add generic methods to get data from APIs.
Add a new folder under src folder name "Service" and add new file "ServiceProvider.ts"
Add the below code to SeviceProvider.ts file.
- import { HttpClient, IHttpClientOptions } from '@microsoft/sp-http';
- import { WebPartContext } from '@microsoft/sp-webpart-base';
-
- export class ServiceProvider {
- private ctx: WebPartContext;
-
-
-
-
- public constructor(context: WebPartContext) {
- this.ctx = context;
- }
-
-
-
-
- private httpClientHeaders: IHttpClientOptions = {
- headers: new Headers({
- "accept": "application/json"
- }),
- method: "GET",
- mode: "cors"
- };
-
-
-
-
-
-
- public async getAllDistricts() {
- var response = await this.ctx.httpClient.
- get("https://cdn-api.co-vin.in/api/v2/admin/location/districts/11",
- HttpClient.configurations.v1, this.httpClientHeaders);
- var responseJson: any = await response.json();
- return responseJson;
- }
-
-
-
-
-
-
-
- public async getVaccinationSlots(districtId, selectedDate) {
- var endPoint = `https:
- var response = await this.ctx.httpClient
- .get(endPoint,
- HttpClient.configurations.v1, this.httpClientHeaders);
-
- var responseJson: any = await response.json();
- return responseJson;
-
- }
-
-
-
-
-
-
-
- public async getVaccinationSlotsByPincode(pincode, selectedDate) {
- var endPoint = `https:
- var response = await this.ctx.httpClient.get(endPoint, HttpClient.configurations.v1,
- this.httpClientHeaders);
- var responseJson: any = await response.json();
- return responseJson;
- }
- }
Step 4
Create a new file under the component with the name IVaccineSlotsState.ts to manage the state in React Application.
Add the below code in IVaccineSlotsState.ts
- import { IComboBoxOption } from "@fluentui/react";
-
- export interface IVaccineSlotsState{
- isLoading:boolean;
- SelectedDate:Date;
- districtsOptions:IComboBoxOption[];
- availableCenters:IComboBoxOption[];
- centers:any[];
- SelectedDistrictID: string | number;
- isSearchByPinCode:boolean;
- isSearchByDistricts:boolean;
- pincode:number;
- selectedCenterId:string|number;
- slots:any[];
- }
Step 5
Make the below changes in IVaccineSlotsProps.ts file.
- import { WebPartContext } from '@microsoft/sp-webpart-base';
- export interface IVaccineSlotsProps {
- description: string;
- context:WebPartContext;
- }
Step 6
Make the below changes in Render() to webpart.ts file to assign context which will be used in the later portion of the development.
- const element: React.ReactElement<IVaccineSlotsProps> = React.createElement(
- VaccineSlots,
- {
- description: this.properties.description,
- context:this.context
- }
- );
Step 7
Open VaccineSlots.ts file and make the below changes in an existing file.
Import the below packages on top of the file.
- import * as React from 'react';
- import styles from './VaccineSlots.module.scss';
- import { IVaccineSlotsProps } from './IVaccineSlotsProps';
- import { IVaccineSlotsState } from "./IVaccineSlotsState";
- import { ServiceProvider } from "../../../Service/ServiceProvide"
- import { Toggle } from '@fluentui/react/lib/Toggle';
- import { TextField } from '@fluentui/react/lib/TextField';
- import {
- ComboBox,
- Stack,
- IComboBoxOption,
- IStackTokens,
- IComboBoxStyles,
- StackItem,
- } from '@fluentui/react';
- import { PrimaryButton } from '@fluentui/react/lib/Button';
- import { DateTimePicker, DateConvention, TimeConvention } from '@pnp/spfx-controls-react/lib/DateTimePicker';
- import * as moment from 'moment';
- require('moment');
- import Paper from '@material-ui/core/Paper';
- import { Grid, Table, TableHeaderRow } from '@devexpress/dx-react-grid-material-ui';
-
- const comboBoxStyles: Partial<IComboBoxStyles> = { root: { maxWidth: 300 } };
- const stackTokens: Partial<IStackTokens> = { childrenGap: 20 };
-
-
-
-
- const columns = [
- { name: "name", title: "Name" },
- { name: "Address", title: "Address" },
- { name: "centerId", title: "CenterID" },
- { name: "fee_type", title: "Fee Type" },
- { name: 'Capacity', title: 'Capacity' },
- { name: 'limit', title: 'Age Limit' },
- { name: 'vaccine', title: 'Type' },
- ];
Add Constructor in React Component,
- constructor(props) {
- super(props);
-
-
-
- this.serviceProvider = new ServiceProvider(this.props.context);
-
-
-
- this.state = {
- isLoading: false,
- districtsOptions: [],
- availableCenters: [],
- SelectedDate: new Date(),
- centers: [],
- SelectedDistrictID: 0,
- isSearchByPinCode: false,
- isSearchByDistricts: true,
- pincode: 0,
- selectedCenterId: '',
- slots: []
- }
-
-
-
- this.__onchangedStartDate = this.__onchangedStartDate.bind(this);
- this.getSlotsByDistricts = this.getSlotsByDistricts.bind(this);
- this._onChange = this._onChange.bind(this);
- this.onPincodechange = this.onPincodechange.bind(this);
- this.getSlotsByPincode = this.getSlotsByPincode.bind(this);
- }
Add the below code in Render(),
- return (
- <div className={styles.vaccineSlots}>
- <Stack horizontal tokens={stackTokens}>
- <Toggle defaultChecked={false} onText="By Pincode" offText="By Districts" onChange={this._onChange} />
- </Stack>
- {this.state.isSearchByDistricts == true &&
- <Stack horizontal tokens={stackTokens}>
- <StackItem>
- <ComboBox
- label="Select District "
- allowFreeform={true}
- autoComplete='on'
- options={this.state.districtsOptions}
- styles={comboBoxStyles}
- onChange={(event, option) => {
- this.setState({ SelectedDistrictID: option.key })
- }}
- />
- </StackItem>
- <StackItem>
- <DateTimePicker
- label="Select Date"
- dateConvention={DateConvention.Date}
- timeConvention={TimeConvention.Hours24}
- value={this.state.SelectedDate}
- showLabels={false}
- onChange={this.__onchangedStartDate} />
- </StackItem>
- <StackItem>
- <PrimaryButton style={{ marginTop: "28px" }} text="Search" onClick={this.getSlotsByDistricts} />
- </StackItem>
- </Stack>}
- {this.state.isSearchByPinCode == true &&
- <Stack horizontal tokens={stackTokens}>
- <StackItem>
- <TextField
- label="Enter Pincode"
- style={{ width: 100 }}
- onChange={this.onPincodechange}
- />
- </StackItem>
- <StackItem>
- <DateTimePicker
- label="Select Date"
- dateConvention={DateConvention.Date}
- timeConvention={TimeConvention.Hours24}
- value={this.state.SelectedDate}
- showLabels={false}
- onChange={this.__onchangedStartDate} />
- </StackItem>
- <StackItem>
- <PrimaryButton style={{ marginTop: "28px" }} text="Search" onClick={this.getSlotsByPincode} />
- </StackItem>
- </Stack>
- }
- {
- this.state.slots.length > 0 &&
- <Stack>
- <Paper>
- <Grid
- rows={this.state.slots}
- columns={columns}
- >
- <Table />
- <TableHeaderRow />
- </Grid>
- </Paper>
- </Stack>
- }
- </div>
- );
- }
Create the below methods in React component class.
-
-
-
-
-
- private onPincodechange(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) {
- this.setState({ pincode: parseInt(newValue) })
- }
-
-
-
-
-
-
- private _onChange(ev: React.MouseEvent<HTMLElement>, checked?: boolean) {
- if (checked) {
- this.setState({ isSearchByPinCode: true, isSearchByDistricts: false });
- }
- else {
- this.getDistricts();
- this.setState({
- isSearchByDistricts: true,
- isSearchByPinCode: false
- })
- }
- }
-
-
-
-
- private getSlotsByPincode() {
- var selectedDate = moment(this.state.SelectedDate.toString()).format("DD-MM-YYYY")
- this.serviceProvider.getVaccinationSlotsByPincode(this.state.pincode.toString(), selectedDate)
- .then(
- (result: any): void => {
- if (result.sessions !== undefined && result.sessions.length > 0) {
- this.setState({ centers: result.sessions })
- var tempRows = []
- result.sessions.forEach(e => {
- tempRows.push({
- name: e.name,
- Address: e.address,
- centerId: e.center_id,
- fee_type: e.fee_type,
- Capacity: e.available_capacity,
- limit: e.min_age_limit,
- vaccine: e.vaccine
- });
- });
- this.setState({ slots: tempRows });
- }
- else {
- this.setState({ slots: [] })
- }
- }
- )
- .catch(error => {
- console.log(error);
- });
- }
-
-
-
-
- private getSlotsByDistricts() {
- var selectedDate = moment(this.state.SelectedDate.toString()).format("DD-MM-YYYY")
- this.serviceProvider.
- getVaccinationSlots(this.state.SelectedDistrictID, selectedDate)
- .then((result: any): void => {
- if (result.centers !== undefined && result.centers.length > 0) {
- this.setState({ centers: result.centers })
- var tempRows = []
- result.centers.forEach(e => {
- var selectedSession = e.sessions.filter(t => t.date == selectedDate);
- tempRows.push({
- name: e.name,
- Address: e.address,
- centerId: e.center_id,
- fee_type: e.fee_type,
- Capacity: selectedSession[0].available_capacity,
- limit: selectedSession[0].min_age_limit,
- vaccine: selectedSession[0].vaccine
- });
- });
- this.setState({ slots: tempRows });
- }
- else {
- this.setState({ slots: [] })
- }
- }).catch(error => {
- console.log(error);
- this.setState({ slots: [] })
- });
- }
-
-
-
-
-
- private __onchangedStartDate(date: any): void {
- this.setState({ SelectedDate: date });
- }
-
-
-
-
- private getDistricts() {
- this.serviceProvider.
- getAllDistricts()
- .then(
- (result: any): void => {
- var tempOptions: IComboBoxOption[] = [];
- result.districts.forEach(e => {
- tempOptions.push({ key: e.district_id, text: e.district_name })
- });
- this.setState({ districtsOptions: tempOptions });
- }
- )
- .catch(error => {
- console.log(error);
- });
- }
- public async componentDidMount() {
- this.getDistricts();
- }
Step 8
Open VaccineSlotsWebpart.manifest.json file and add below code in the existing file before preconfiguredentries
- "supportedHosts": ["SharePointWebPart","TeamsTab"],
Step 9
Open package-solution.json file from config folder and add below line in file.
- "skipFeatureDeployment": true,
Step 10
Run below command,
- gulp clean build
- gulp bundle --ship
- gulp package-solution --ship
Once command run successfully, you will get .sppkg file under "CheckVaccineSlots\sharepoint\solution".
Step 11
Deploy sppkg file to app catalog in your site.
Click here to know how to upload sppkg file in App catalog
Step 12
After sppkg file is successfully deployed, open App catalog and follow the below steps.
Click Here to know how to add SPFx solution in Teams Tab.
Step 13
Add app in Microsoft Teams.
Final Output
Note for APIs perspective,
- The arrangement accessibility information is reserved and might be up to 30 minutes old
- 100 APIs calls per 5 minutes per IP.
HappyCoding