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.
Click here to read about API Setu.
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.
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.
- //use for display records in grid from Material UI
- npm i --save @devexpress/dx-react-core @devexpress/dx-react-grid
- //use for display records in grid from Material UI
- npm install @material-ui/core
- //use for display records in grid from Material UI
- npm i @material-ui/icons/ChevronLeft
- //use to use button, Textfield and combobox control for selection
- npm i @fluentui/react
- // Use to add datetime control
- npm i @pnp/spfx-controls-react
- // use for date manipulation and formattion
- 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 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;
- /**
- * To get context from web part to use inbuilt library from SPFx
- * @param context WebpartContext
- */
- public constructor(context: WebPartContext) {
- this.ctx = context;
- }
- /**
- * Inilize client Headers which we need to use while sending request to API
- */
- private httpClientHeaders: IHttpClientOptions = {
- headers: new Headers({
- "accept": "application/json"
- }),
- method: "GET",
- mode: "cors"
- };
- /**
- *
- * @returns get All districts for selected state ID.
- * Here I put 11 for Gujarat only. You can change it according to you.
- */
- 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;
- }
- /**
- * Get available vaccination slots by selected district Id and selected date
- * @param districtId
- * @param selectedDate
- * @returns
- */
- public async getVaccinationSlots(districtId, selectedDate) {
- var endPoint = `https://cdn-api.co-vin.in/api/v2/appointment/sessions/calendarByDistrict?district_id=${districtId}&date=${selectedDate}`;
- var response = await this.ctx.httpClient
- .get(endPoint,
- HttpClient.configurations.v1, this.httpClientHeaders);
- var responseJson: any = await response.json();
- return responseJson;
- }
- /**
- * get available slots by selected pincode and selected date
- * @param pincode
- * @param selectedDate
- * @returns
- */
- public async getVaccinationSlotsByPincode(pincode, selectedDate) {
- var endPoint = `https://cdn-api.co-vin.in/api/v2/appointment/sessions/public/findByPin?pincode=${pincode}&date=${selectedDate}`;
- 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

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 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 };
- /**
- * Columns is used to display column in material UI grid.
- */
- 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);
- /**
- * Make instance of service provider to access methods
- */
- this.serviceProvider = new ServiceProvider(this.props.context);
- /**
- * Initialize default state value.
- */
- this.state = {
- isLoading: false,
- districtsOptions: [],
- availableCenters: [],
- SelectedDate: new Date(),
- centers: [],
- SelectedDistrictID: 0,
- isSearchByPinCode: false,
- isSearchByDistricts: true,
- pincode: 0,
- selectedCenterId: '',
- slots: []
- }
- /**
- * Initialize methods definitions.
- */
- 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.
- /**
- * Get new value from Text field control and set it in state in Pincode property
- * @param event
- * @param newValue
- */
- private onPincodechange(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) {
- this.setState({ pincode: parseInt(newValue) })
- }
- /**
- * Get value from selected toggle button, to identify user has selected by pincode or by district
- * @param ev
- * @param checked
- */
- 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
- })
- }
- }
- /**
- * Get Slots by pincode number
- */
- 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);
- });
- }
- /**
- * Get slots by District ID
- */
- 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: [] })
- });
- }
- /**
- * Get new selected date and add it in State
- * @param date
- */
- private __onchangedStartDate(date: any): void {
- this.setState({ SelectedDate: date });
- }
- /**
- * Get district on Toogle change event
- */
- 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.
Reference URL
HappyCoding

Join the conversation! Your thoughts help the community grow.