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. export class ServiceProvider {
  4. private ctx: WebPartContext;
  5. /**
  6. * To get context from web part to use inbuilt library from SPFx
  7. * @param context WebpartContext
  8. */
  9. public constructor(context: WebPartContext) {
  10. this.ctx = context;
  11. }
  12. /**
  13. * Inilize client Headers which we need to use while sending request to API
  14. */
  15. private httpClientHeaders: IHttpClientOptions = {
  16. headers: new Headers({
  17. "accept": "application/json"
  18. }),
  19. method: "GET",
  20. mode: "cors"
  21. };
  22. /**
  23. *
  24. * @returns get All districts for selected state ID.
  25. * Here I put 11 for Gujarat only. You can change it according to you.
  26. */
  27. public async getAllDistricts() {
  28. var response = await this.ctx.httpClient.
  29. get("https://cdn-api.co-vin.in/api/v2/admin/location/districts/11",
  30. HttpClient.configurations.v1, this.httpClientHeaders);
  31. var responseJson: any = await response.json();
  32. return responseJson;
  33. }
  34. /**
  35. * Get available vaccination slots by selected district Id and selected date
  36. * @param districtId
  37. * @param selectedDate
  38. * @returns
  39. */
  40. public async getVaccinationSlots(districtId, selectedDate) {
  41. var endPoint = `https://cdn-api.co-vin.in/api/v2/appointment/sessions/calendarByDistrict?district_id=${districtId}&date=${selectedDate}`;
  42. var response = await this.ctx.httpClient
  43. .get(endPoint,
  44. HttpClient.configurations.v1, this.httpClientHeaders);
  45. var responseJson: any = await response.json();
  46. return responseJson;
  47. }
  48. /**
  49. * get available slots by selected pincode and selected date
  50. * @param pincode
  51. * @param selectedDate
  52. * @returns
  53. */
  54. public async getVaccinationSlotsByPincode(pincode, selectedDate) {
  55. var endPoint = `https://cdn-api.co-vin.in/api/v2/appointment/sessions/public/findByPin?pincode=${pincode}&date=${selectedDate}`;
  56. var response = await this.ctx.httpClient.get(endPoint, HttpClient.configurations.v1,
  57. this.httpClientHeaders);
  58. var responseJson: any = await response.json();
  59. return responseJson;
  60. }
  61. }
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. export interface IVaccineSlotsState{
  3. isLoading:boolean;
  4. SelectedDate:Date;
  5. districtsOptions:IComboBoxOption[];
  6. availableCenters:IComboBoxOption[];
  7. centers:any[];
  8. SelectedDistrictID: string | number;
  9. isSearchByPinCode:boolean;
  10. isSearchByDistricts:boolean;
  11. pincode:number;
  12. selectedCenterId:string|number;
  13. slots:any[];
  14. }
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. const comboBoxStyles: Partial<IComboBoxStyles> = { root: { maxWidth: 300 } };
  23. const stackTokens: Partial<IStackTokens> = { childrenGap: 20 };
  24. /**
  25. * Columns is used to display column in material UI grid.
  26. */
  27. const columns = [
  28. { name: "name", title: "Name" },
  29. { name: "Address", title: "Address" },
  30. { name: "centerId", title: "CenterID" },
  31. { name: "fee_type", title: "Fee Type" },
  32. { name: 'Capacity', title: 'Capacity' },
  33. { name: 'limit', title: 'Age Limit' },
  34. { name: 'vaccine', title: 'Type' },
  35. ];
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. * Get value from selected toggle button, to identify user has selected by pincode or by district
  11. * @param ev
  12. * @param checked
  13. */
  14. private _onChange(ev: React.MouseEvent<HTMLElement>, checked?: boolean) {
  15. if (checked) {
  16. this.setState({ isSearchByPinCode: true, isSearchByDistricts: false });
  17. }
  18. else {
  19. this.getDistricts();
  20. this.setState({
  21. isSearchByDistricts: true,
  22. isSearchByPinCode: false
  23. })
  24. }
  25. }
  26. /**
  27. * Get Slots by pincode number
  28. */
  29. private getSlotsByPincode() {
  30. var selectedDate = moment(this.state.SelectedDate.toString()).format("DD-MM-YYYY")
  31. this.serviceProvider.getVaccinationSlotsByPincode(this.state.pincode.toString(), selectedDate)
  32. .then(
  33. (result: any): void => {
  34. if (result.sessions !== undefined && result.sessions.length > 0) {
  35. this.setState({ centers: result.sessions })
  36. var tempRows = []
  37. result.sessions.forEach(e => {
  38. tempRows.push({
  39. name: e.name,
  40. Address: e.address,
  41. centerId: e.center_id,
  42. fee_type: e.fee_type,
  43. Capacity: e.available_capacity,
  44. limit: e.min_age_limit,
  45. vaccine: e.vaccine
  46. });
  47. });
  48. this.setState({ slots: tempRows });
  49. }
  50. else {
  51. this.setState({ slots: [] })
  52. }
  53. }
  54. )
  55. .catch(error => {
  56. console.log(error);
  57. });
  58. }
  59. /**
  60. * Get slots by District ID
  61. */
  62. private getSlotsByDistricts() {
  63. var selectedDate = moment(this.state.SelectedDate.toString()).format("DD-MM-YYYY")
  64. this.serviceProvider.
  65. getVaccinationSlots(this.state.SelectedDistrictID, selectedDate)
  66. .then((result: any): void => {
  67. if (result.centers !== undefined && result.centers.length > 0) {
  68. this.setState({ centers: result.centers })
  69. var tempRows = []
  70. result.centers.forEach(e => {
  71. var selectedSession = e.sessions.filter(t => t.date == selectedDate);
  72. tempRows.push({
  73. name: e.name,
  74. Address: e.address,
  75. centerId: e.center_id,
  76. fee_type: e.fee_type,
  77. Capacity: selectedSession[0].available_capacity,
  78. limit: selectedSession[0].min_age_limit,
  79. vaccine: selectedSession[0].vaccine
  80. });
  81. });
  82. this.setState({ slots: tempRows });
  83. }
  84. else {
  85. this.setState({ slots: [] })
  86. }
  87. }).catch(error => {
  88. console.log(error);
  89. this.setState({ slots: [] })
  90. });
  91. }
  92. /**
  93. * Get new selected date and add it in State
  94. * @param date
  95. */
  96. private __onchangedStartDate(date: any): void {
  97. this.setState({ SelectedDate: date });
  98. }
  99. /**
  100. * Get district on Toogle change event
  101. */
  102. private getDistricts() {
  103. this.serviceProvider.
  104. getAllDistricts()
  105. .then(
  106. (result: any): void => {
  107. var tempOptions: IComboBoxOption[] = [];
  108. result.districts.forEach(e => {
  109. tempOptions.push({ key: e.district_id, text: e.district_name })
  110. });
  111. this.setState({ districtsOptions: tempOptions });
  112. }
  113. )
  114. .catch(error => {
  115. console.log(error);
  116. });
  117. }
  118. public async componentDidMount() {
  119. this.getDistricts();
  120. }
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.
Reference URL
HappyCoding