Check Availability Of Vaccination Slot In Microsoft Teams Tab

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


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.
  • Solution Name: CheckVaccineSlots
  • WebPart Name: VaccineSlotsWebpart
  • Primary Component: VaccineSlots
Check Availability Of Vaccination Slot In Microsoft Teams Tab
 
Step 2
 
Install the below required packages to solution.
  1. //use for display records in grid from Material UI   
  2. npm i --save @devexpress/dx-react-core @devexpress/dx-react-grid  
  3. //use for display records in grid from Material UI  
  4. npm install @material-ui/core    
  5. //use for display records in grid from Material UI  
  6. npm i @material-ui/icons/ChevronLeft      
  7. //use to use button, Textfield and combobox control for selection  
  8. npm i @fluentui/react  
  9. // Use to add datetime control  
  10. npm i @pnp/spfx-controls-react  
  11. // use for date manipulation and formattion  
  12. 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"

Check Availability Of Vaccination Slot In Microsoft Teams Tab

Add the below code to SeviceProvider.ts file.
  1. import { HttpClient, IHttpClientOptions } from '@microsoft/sp-http';  
  2. import { WebPartContext } from '@microsoft/sp-webpart-base';  
  3.   
  4. export class ServiceProvider {  
  5.   private ctx: WebPartContext;  
  6.   /** 
  7.    * To get context from web part to use inbuilt library from SPFx 
  8.    * @param context WebpartContext 
  9.    */  
  10.   public constructor(context: WebPartContext) {  
  11.     this.ctx = context;  
  12.   }  
  13.   
  14.   /** 
  15.    * Inilize client Headers which we need to use while sending request to API 
  16.    */  
  17.   private httpClientHeaders: IHttpClientOptions = {  
  18.     headers: new Headers({  
  19.       "accept""application/json"  
  20.     }),  
  21.     method: "GET",  
  22.     mode: "cors"  
  23.   };  
  24.   
  25.   /** 
  26.    * 
  27.    * @returns get All districts for selected state ID. 
  28.    * Here I put 11 for Gujarat only. You can change it according to you. 
  29.    */  
  30.   public async getAllDistricts() {  
  31.     var response = await this.ctx.httpClient.  
  32.       get("https://cdn-api.co-vin.in/api/v2/admin/location/districts/11",  
  33.         HttpClient.configurations.v1, this.httpClientHeaders);  
  34.     var responseJson: any = await response.json();  
  35.     return responseJson;  
  36.   }  
  37.   
  38.   /** 
  39.    * Get available vaccination slots by selected district Id and selected date 
  40.    * @param districtId  
  41.    * @param selectedDate  
  42.    * @returns  
  43.    */  
  44.   public async getVaccinationSlots(districtId, selectedDate) {  
  45.     var endPoint = `https://cdn-api.co-vin.in/api/v2/appointment/sessions/calendarByDistrict?district_id=${districtId}&date=${selectedDate}`;  
  46.     var response = await this.ctx.httpClient  
  47.       .get(endPoint,  
  48.         HttpClient.configurations.v1, this.httpClientHeaders);  
  49.   
  50.     var responseJson: any = await response.json();  
  51.     return responseJson;  
  52.   
  53.   }  
  54.   
  55.   /** 
  56.    * get available slots by selected pincode and selected date 
  57.    * @param pincode  
  58.    * @param selectedDate  
  59.    * @returns  
  60.    */  
  61.   public async getVaccinationSlotsByPincode(pincode, selectedDate) {  
  62.     var endPoint = `https://cdn-api.co-vin.in/api/v2/appointment/sessions/public/findByPin?pincode=${pincode}&date=${selectedDate}`;  
  63.     var response = await this.ctx.httpClient.get(endPoint, HttpClient.configurations.v1,  
  64.       this.httpClientHeaders);  
  65.     var responseJson: any = await response.json();  
  66.     return responseJson;  
  67.   }  
  68. }  
Step 4
 
Create a new file under the component with the name IVaccineSlotsState.ts to manage the state in React Application.

Check Availability Of Vaccination Slot In Microsoft Teams Tab

Add the below code in IVaccineSlotsState.ts
  1. import { IComboBoxOption } from "@fluentui/react";  
  2.   
  3. export interface IVaccineSlotsState{  
  4.   isLoading:boolean;  
  5.   SelectedDate:Date;  
  6.   districtsOptions:IComboBoxOption[];  
  7.   availableCenters:IComboBoxOption[];  
  8.   centers:any[];  
  9.   SelectedDistrictID: string | number;  
  10.   isSearchByPinCode:boolean;  
  11.   isSearchByDistricts:boolean;  
  12.   pincode:number;  
  13.   selectedCenterId:string|number;  
  14.   slots:any[];  
  15. }  
Step 5
 
Make the below changes in IVaccineSlotsProps.ts file.
  1. import { WebPartContext } from '@microsoft/sp-webpart-base';  
  2. export interface IVaccineSlotsProps {  
  3.   description: string;  
  4.   context:WebPartContext;  
  5. }  
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.

Check Availability Of Vaccination Slot In Microsoft Teams Tab
  1. const element: React.ReactElement<IVaccineSlotsProps> = React.createElement(  
  2.      VaccineSlots,  
  3.      {  
  4.        description: this.properties.description,  
  5.        context:this.context  
  6.      }  
  7.    );  
Step 7
 
Open VaccineSlots.ts file and make the below changes in an existing file.

Check Availability Of Vaccination Slot In Microsoft Teams Tab

Import the below packages on top of the file.
  1. import * as React from 'react';  
  2. import styles from './VaccineSlots.module.scss';  
  3. import { IVaccineSlotsProps } from './IVaccineSlotsProps';  
  4. import { IVaccineSlotsState } from "./IVaccineSlotsState";  
  5. import { ServiceProvider } from "../../../Service/ServiceProvide"  
  6. import { Toggle } from '@fluentui/react/lib/Toggle';  
  7. import { TextField } from '@fluentui/react/lib/TextField';  
  8. import {  
  9.   ComboBox,  
  10.   Stack,  
  11.   IComboBoxOption,  
  12.   IStackTokens,  
  13.   IComboBoxStyles,  
  14.   StackItem,  
  15. } from '@fluentui/react';  
  16. import { PrimaryButton } from '@fluentui/react/lib/Button';  
  17. import { DateTimePicker, DateConvention, TimeConvention } from '@pnp/spfx-controls-react/lib/DateTimePicker';  
  18. import * as moment from 'moment';  
  19. require('moment');  
  20. import Paper from '@material-ui/core/Paper';  
  21. import { Grid, Table, TableHeaderRow } from '@devexpress/dx-react-grid-material-ui';  
  22.   
  23. const comboBoxStyles: Partial<IComboBoxStyles> = { root: { maxWidth: 300 } };  
  24. const stackTokens: Partial<IStackTokens> = { childrenGap: 20 };  
  25.   
  26. /** 
  27.  * Columns is used to display column in material UI grid. 
  28.  */  
  29. const columns = [  
  30.   { name: "name", title: "Name" },  
  31.   { name: "Address", title: "Address" },  
  32.   { name: "centerId", title: "CenterID" },  
  33.   { name: "fee_type", title: "Fee Type" },  
  34.   { name: 'Capacity', title: 'Capacity' },  
  35.   { name: 'limit', title: 'Age Limit' },  
  36.   { name: 'vaccine', title: 'Type' },  
  37. ];  
Add Constructor in React Component,
  1. constructor(props) {  
  2.     super(props);  
  3.     /** 
  4.      * Make instance of service provider to access methods 
  5.      */  
  6.     this.serviceProvider = new ServiceProvider(this.props.context);  
  7.     /** 
  8.      * Initialize default state value. 
  9.      */  
  10.     this.state = {  
  11.       isLoading: false,  
  12.       districtsOptions: [],  
  13.       availableCenters: [],  
  14.       SelectedDate: new Date(),  
  15.       centers: [],  
  16.       SelectedDistrictID: 0,  
  17.       isSearchByPinCode: false,  
  18.       isSearchByDistricts: true,  
  19.       pincode: 0,  
  20.       selectedCenterId: '',  
  21.       slots: []  
  22.     }  
  23.     /** 
  24.      * Initialize methods definitions. 
  25.      */  
  26.     this.__onchangedStartDate = this.__onchangedStartDate.bind(this);  
  27.     this.getSlotsByDistricts = this.getSlotsByDistricts.bind(this);  
  28.     this._onChange = this._onChange.bind(this);  
  29.     this.onPincodechange = this.onPincodechange.bind(this);  
  30.     this.getSlotsByPincode = this.getSlotsByPincode.bind(this);  
  31.   }  
Add the below code in Render(),
  1. return (  
  2.       <div className={styles.vaccineSlots}>  
  3.         <Stack horizontal tokens={stackTokens}>  
  4.           <Toggle defaultChecked={false} onText="By Pincode" offText="By Districts" onChange={this._onChange} />  
  5.         </Stack>  
  6.         {this.state.isSearchByDistricts == true &&  
  7.           <Stack horizontal tokens={stackTokens}>  
  8.             <StackItem>  
  9.               <ComboBox  
  10.                 label="Select District "  
  11.                 allowFreeform={true}  
  12.                 autoComplete='on'  
  13.                 options={this.state.districtsOptions}  
  14.                 styles={comboBoxStyles}  
  15.                 onChange={(event, option) => {  
  16.                   this.setState({ SelectedDistrictID: option.key })  
  17.                 }}  
  18.               />  
  19.             </StackItem>  
  20.             <StackItem>  
  21.               <DateTimePicker  
  22.                 label="Select Date"  
  23.                 dateConvention={DateConvention.Date}  
  24.                 timeConvention={TimeConvention.Hours24}  
  25.                 value={this.state.SelectedDate}  
  26.                 showLabels={false}  
  27.                 onChange={this.__onchangedStartDate} />  
  28.             </StackItem>  
  29.             <StackItem>  
  30.               <PrimaryButton style={{ marginTop: "28px" }} text="Search" onClick={this.getSlotsByDistricts} />  
  31.             </StackItem>  
  32.           </Stack>}  
  33.         {this.state.isSearchByPinCode == true &&  
  34.           <Stack horizontal tokens={stackTokens}>  
  35.             <StackItem>  
  36.               <TextField  
  37.                 label="Enter Pincode"  
  38.                 style={{ width: 100 }}  
  39.                 onChange={this.onPincodechange}  
  40.               />  
  41.             </StackItem>  
  42.             <StackItem>  
  43.               <DateTimePicker  
  44.                 label="Select Date"  
  45.                 dateConvention={DateConvention.Date}  
  46.                 timeConvention={TimeConvention.Hours24}  
  47.                 value={this.state.SelectedDate}  
  48.                 showLabels={false}  
  49.                 onChange={this.__onchangedStartDate} />  
  50.             </StackItem>  
  51.             <StackItem>  
  52.               <PrimaryButton style={{ marginTop: "28px" }} text="Search" onClick={this.getSlotsByPincode} />  
  53.             </StackItem>  
  54.           </Stack>  
  55.         }  
  56.         {  
  57.           this.state.slots.length > 0 &&  
  58.           <Stack>  
  59.             <Paper>  
  60.               <Grid  
  61.                 rows={this.state.slots}  
  62.                 columns={columns}  
  63.               >  
  64.                 <Table />  
  65.                 <TableHeaderRow />  
  66.               </Grid>  
  67.             </Paper>  
  68.           </Stack>  
  69.         }  
  70.       </div>  
  71.     );  
  72.   }  
Create the below methods in React component class.
  1. /** 
  2.   * Get new value from Text field control and set it in state in Pincode property 
  3.   * @param event 
  4.   * @param newValue 
  5.   */  
  6.  private onPincodechange(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) {  
  7.    this.setState({ pincode: parseInt(newValue) })  
  8.  }  
  9.   
  10.  /** 
  11.   * Get value from selected toggle button, to identify user has selected by pincode or by district 
  12.   * @param ev 
  13.   * @param checked 
  14.   */  
  15.  private _onChange(ev: React.MouseEvent<HTMLElement>, checked?: boolean) {  
  16.    if (checked) {  
  17.      this.setState({ isSearchByPinCode: true, isSearchByDistricts: false });  
  18.    }  
  19.    else {  
  20.      this.getDistricts();  
  21.      this.setState({  
  22.        isSearchByDistricts: true,  
  23.        isSearchByPinCode: false  
  24.      })  
  25.    }  
  26.  }  
  27.   
  28.  /** 
  29.   * Get Slots by pincode number 
  30.   */  
  31.  private getSlotsByPincode() {  
  32.    var selectedDate = moment(this.state.SelectedDate.toString()).format("DD-MM-YYYY")  
  33.    this.serviceProvider.getVaccinationSlotsByPincode(this.state.pincode.toString(), selectedDate)  
  34.      .then(  
  35.        (result: any): void => {  
  36.          if (result.sessions !== undefined && result.sessions.length > 0) {  
  37.            this.setState({ centers: result.sessions })  
  38.            var tempRows = []  
  39.            result.sessions.forEach(e => {  
  40.              tempRows.push({  
  41.                name: e.name,  
  42.                Address: e.address,  
  43.                centerId: e.center_id,  
  44.                fee_type: e.fee_type,  
  45.                Capacity: e.available_capacity,  
  46.                limit: e.min_age_limit,  
  47.                vaccine: e.vaccine  
  48.              });  
  49.            });  
  50.            this.setState({ slots: tempRows });  
  51.          }  
  52.          else {  
  53.            this.setState({ slots: [] })  
  54.          }  
  55.        }  
  56.      )  
  57.      .catch(error => {  
  58.        console.log(error);  
  59.      });  
  60.  }  
  61.   
  62.  /** 
  63.   * Get slots by District ID 
  64.   */  
  65.  private getSlotsByDistricts() {  
  66.    var selectedDate = moment(this.state.SelectedDate.toString()).format("DD-MM-YYYY")  
  67.    this.serviceProvider.  
  68.      getVaccinationSlots(this.state.SelectedDistrictID, selectedDate)  
  69.      .then((result: any): void => {  
  70.        if (result.centers !== undefined && result.centers.length > 0) {  
  71.          this.setState({ centers: result.centers })  
  72.          var tempRows = []  
  73.          result.centers.forEach(e => {  
  74.            var selectedSession = e.sessions.filter(t => t.date == selectedDate);  
  75.            tempRows.push({  
  76.              name: e.name,  
  77.              Address: e.address,  
  78.              centerId: e.center_id,  
  79.              fee_type: e.fee_type,  
  80.              Capacity: selectedSession[0].available_capacity,  
  81.              limit: selectedSession[0].min_age_limit,  
  82.              vaccine: selectedSession[0].vaccine  
  83.            });  
  84.          });  
  85.          this.setState({ slots: tempRows });  
  86.        }  
  87.        else {  
  88.          this.setState({ slots: [] })  
  89.        }  
  90.      }).catch(error => {  
  91.        console.log(error);  
  92.        this.setState({ slots: [] })  
  93.      });  
  94.  }  
  95.  /** 
  96.   * Get new selected date and add it in State 
  97.   * @param date 
  98.   */  
  99.   
  100.  private __onchangedStartDate(date: any): void {  
  101.    this.setState({ SelectedDate: date });  
  102.  }  
  103.   
  104.  /** 
  105.   * Get district on Toogle change event 
  106.   */  
  107.  private getDistricts() {  
  108.    this.serviceProvider.  
  109.      getAllDistricts()  
  110.      .then(  
  111.        (result: any): void => {  
  112.          var tempOptions: IComboBoxOption[] = [];  
  113.          result.districts.forEach(e => {  
  114.            tempOptions.push({ key: e.district_id, text: e.district_name })  
  115.          });  
  116.          this.setState({ districtsOptions: tempOptions });  
  117.        }  
  118.      )  
  119.      .catch(error => {  
  120.        console.log(error);  
  121.      });  
  122.  }  
  123.  public async componentDidMount() {  
  124.    this.getDistricts();  
  125.  }  
Step 8
 
Open VaccineSlotsWebpart.manifest.json file and add below code in the existing file before preconfiguredentries

Check Availability Of Vaccination Slot In Microsoft Teams Tab
  1. "supportedHosts": ["SharePointWebPart","TeamsTab"],  
Step 9
 
Open package-solution.json file from config folder and add below line in file.
  1. "skipFeatureDeployment"true,  
Step 10
 
Run below command,
  1. gulp clean build    
  2. gulp bundle --ship    
  3. gulp package-solution --ship     
Once command run successfully, you will get .sppkg file under "CheckVaccineSlots\sharepoint\solution".
 
Check Availability Of Vaccination Slot In Microsoft Teams Tab
 
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.

Check Availability Of Vaccination Slot In Microsoft Teams Tab

Click Here to know how to add SPFx solution in Teams Tab.
 
Step 13
 
Add app in Microsoft Teams. 
 
Check Availability Of Vaccination Slot In Microsoft Teams Tab
 
Final Output
 
Check Availability Of Vaccination Slot In Microsoft Teams Tab
Note for APIs perspective,
  1. The arrangement accessibility information is reserved and might be up to 30 minutes old
  2. 100 APIs calls per 5 minutes per IP.
HappyCoding