Medicare Appointment Booking & Scheduling

Business Scenario

In this lab, we will enhance the Medicare application by implementing the appointment booking workflow for patients. 

Pre-Lab Preparation

Module:

1) React Basics & Backend Integration (Spring Boot)​

Phase 12E — Appointment Booking

  • Now we connect:
Find Doctor
    ↓
Doctor Card
    ↓
Book Appointment
    ↓
Select Date
    ↓
Select Slot
    ↓
POST /api/appointments
    ↓
PENDING + UNPAID
    ↓
Booking Confirmation
  • For now we stop at: PENDING / UNPAID because payment is still Phase 13.

Create appointmentService.js

1

  • Create:
src/services/appointmentService.js
import api, {getAuthConfig} from "../api/api";

const createAppointment = async (appointmentData) => {
        const response = await api.post("/appointments",appointmentData,
        getAuthConfig());
        return response.data;
    };

const appointmentService = {createAppointment};

export default appointmentService;
  • For now only one function. We will add:
getMyAppointments()
cancelAppointment()
getDoctorAppointments()
completeAppointment()
  • In the next phase when those screens are implemented.

Update DoctorCard

2

  • Currently the button is disabled.
  • Open:src/components/doctor/DoctorCard.jsx
  • Add:
import {Link} from "react-router";
  • Then replace:
<button className="btn btn-primary w-100" disabled>
    Book Appointment
</button>
  • With :
<Link to={`/patient/doctors/${doctor.id}/book`} className="btn btn-primary w-100">
    Book Appointment
</Link>
  • Your complete DoctorCard.jsx becomes:
import { Link } from "react-router";

function DoctorCard({ doctor }) {
    return (
        <div className="card h-100 shadow-sm">
            {doctor.photoUrl ? (
                <img
                    src={doctor.photoUrl}
                    className="card-img-top"
                    alt={doctor.name}
                    style={{ height: "220px", objectFit: "cover" }}
                />
            ) : (
                <div
                    className="bg-light d-flex align-items-center j
                               ustify-content-center text-secondary"
                    style={{ height: "220px" }}>
                    No Photo
                </div>
            )}

            <div className="card-body d-flex flex-column">
                <h5 className="card-title">{doctor.name}</h5>

                <p className="text-primary fw-semibold mb-2">
                    {doctor.specialization}
                </p>

                <p className="card-text mb-2">
                    <strong>Experience:</strong> {doctor.experienceYears} years
                </p>

                <p className="card-text mb-4">
                    <strong>Consultation Fee:</strong> ₹{doctor.consultationFee}
                </p>

                <Link
                    to={`/patient/doctors/${doctor.id}/book`}
                    className="btn btn-primary w-100 mt-auto"
                >
                    Book Appointment
                </Link>
            </div>
        </div>
    );
}

export default DoctorCard;

Create Booking Page

3

  • Create:
src/pages/patient/BookAppointment.jsx
  • Use :
import { useEffect, useState } from "react";
import { Link, useParams } from "react-router";
import doctorService from "../../services/doctorService";
import appointmentService from "../../services/appointmentService";

function BookAppointment() {
    const { doctorId } = useParams();

    const [doctor, setDoctor] = useState(null);
    const [appointmentDate, setAppointmentDate] = useState("");
    const [timeSlot, setTimeSlot] = useState("");
    const [loadingDoctor, setLoadingDoctor] = useState(true);
    const [booking, setBooking] = useState(false);
    const [error, setError] = useState("");
    const [success, setSuccess] = useState(null);

    useEffect(() => {
        loadDoctor();
    }, [doctorId]);

    const loadDoctor = async () => {
        try {
            setLoadingDoctor(true);
            setError("");

            const data = await doctorService.getDoctorById(doctorId);
            setDoctor(data);
        } catch (error) {
            setError(error.response?.data?.message || 
            "Unable to load doctor");
        } finally {
            setLoadingDoctor(false);
        }
    };

    const getToday = () => {
        const today = new Date();
        const year = today.getFullYear();
        const month = String(today.getMonth() + 1).padStart(2, "0");
        const day = String(today.getDate()).padStart(2, "0");

        return `${year}-${month}-${day}`;
    };

    const handleSubmit = async (event) => {
        event.preventDefault();
        setError("");
        setSuccess(null);

        if (!appointmentDate) {
            setError("Please select an appointment date");
            return;
        }

        if (!timeSlot) {
            setError("Please select a time slot");
            return;
        }

        try {
            setBooking(true);

            const appointmentData = {
                doctorId: Number(doctorId),
                appointmentDate,
                timeSlot
            };

            const response = 
               await appointmentService.createAppointment(appointmentData);
            setSuccess(response);
            setAppointmentDate("");
            setTimeSlot("");
        } catch (error) {
            setError(error.response?.data?.message || 
              "Unable to book appointment");
        } finally {
            setBooking(false);
        }
    };

    if (loadingDoctor) {
        return (
            <div className="container py-5 text-center">
                <div className="spinner-border text-primary"></div>
                <p className="text-secondary mt-3">Loading doctor...</p>
            </div>
        );
    }

    if (!doctor) {
        return (
            <div className="container py-5">
                <div className="alert alert-danger">
                    Doctor information could not be loaded.
                </div>
                <Link to="/patient/doctors" className="btn btn-primary">
                    Back to Doctors
                </Link>
            </div>
        );
    }

    return (
        <div className="container py-5">
            <div className="row justify-content-center">
                <div className="col-lg-8">
                    {/* PAGE HEADER */}
                    <div className="mb-4">
                        <h2>Book Appointment</h2>
                        <p className="text-secondary">
                            Select your preferred appointment date and time.
                        </p>
                    </div>

                    {/* DOCTOR INFORMATION */}
                    <div className="card shadow-sm mb-4">
                        <div className="card-body">
                            <div className="row align-items-center">
                                {doctor.photoUrl && (
                                    <div className="col-md-3 mb-3 mb-md-0">
                                        <img src={doctor.photoUrl} alt={doctor.name} 
                                             className="img-fluid rounded" />
                                    </div>
                                )}

                                <div className={doctor.photoUrl ? "col-md-9" : "col-12"}>
                                    <h4 className="mb-1">{doctor.name}</h4>
                                    <p className="text-primary fw-semibold mb-2">
                                        {doctor.specialization}
                                    </p>
                                    <p className="mb-1">
                                        <strong>Experience:</strong> 
                                      {doctor.experienceYears} years
                                    </p>
                                    <p className="mb-0">
                                        <strong>Consultation Fee:</strong> 
                                      ₹{doctor.consultationFee}
                                    </p>
                                </div>
                            </div>
                        </div>
                    </div>

                    {/* ERROR */}
                    {error && (
                        <div className="alert alert-danger">{error}</div>
                    )}

                    {/* SUCCESS */}
                    {success && (
                        <div className="alert alert-success">
                            <h5>Appointment Booked</h5>
                            <p className="mb-1">
                                Appointment ID: <strong>{success.id}</strong>
                            </p>
                            <p className="mb-1">
                                Doctor: <strong>{success.doctorName}</strong>
                            </p>
                            <p className="mb-1">
                                Date: <strong>{success.appointmentDate}</strong>
                            </p>
                            <p className="mb-1">
                                Slot: <strong>{success.timeSlot}</strong>
                            </p>
                            <p className="mb-1">
                                Status: <strong>{success.appointmentStatus}</strong>
                            </p>
                            <p className="mb-0">
                                Payment: <strong>{success.paymentStatus}</strong>
                            </p>
                        </div>
                    )}

                    {/* BOOKING FORM */}
                    <div className="card shadow-sm">
                        <div className="card-body p-4">
                            <form onSubmit={handleSubmit}>
                                {/* DATE */}
                                <div className="mb-4">
                                    <label className="form-label">
                                      Appointment Date</label>
                                    <input
                                        type="date"
                                        className="form-control"
                                        min={getToday()}
                                        value={appointmentDate}
                                        onChange={(event) => 
                                           setAppointmentDate(event.target.value)}
                                        required
                                    />
                                </div>

                                {/* TIME SLOT */}
                                <div className="mb-4">
                                    <label className="form-label">
                                      Select Time Slot</label>
                                    <div className="row g-3">
                                        {/* 10 AM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotMorning"
                                                value="MORNING_10_AM"
                                                checked={timeSlot === "MORNING_10_AM"}
                                                onChange={(event) => 
                                           setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary w-100" 
                                                   htmlFor="slotMorning">
                                                10:00 AM
                                            </label>
                                        </div>

                                        {/* 2 PM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotAfternoon"
                                                value="AFTERNOON_2_PM"
                                                checked={timeSlot === "AFTERNOON_2_PM"}
                                                onChange={(event) => 
                                                   setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary 
                                                   w-100" htmlFor="slotAfternoon">
                                                2:00 PM
                                            </label>
                                        </div>

                                        {/* 5 PM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotEvening"
                                                value="EVENING_5_PM"
                                                checked={timeSlot === "EVENING_5_PM"}
                                                onChange={(event) => 
                                                    setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary 
                                              w-100" htmlFor="slotEvening">
                                                5:00 PM
                                            </label>
                                        </div>
                                    </div>
                                </div>

                                {/* FEE */}
                                <div className="alert alert-light border">
                                    Consultation Fee: <strong>
                                        ₹{doctor.consultationFee}</strong>
                                </div>

                                <div className="d-flex gap-2">
                                    <Link to="/patient/doctors" 
                                   className="btn btn-outline-secondary">
                                        Back
                                    </Link>
                                    <button
                                        type="submit"
                                        className="btn btn-primary flex-grow-1"
                                        disabled={booking}
                                    >
                                        {booking ? "Booking..." : "Book Appointment"}
                                    </button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default BookAppointment;

function BookAppointment() {
    const { doctorId } = useParams();

    const [doctor, setDoctor] = useState(null);
    const [appointmentDate, setAppointmentDate] = useState("");
    const [timeSlot, setTimeSlot] = useState("");
    const [loadingDoctor, setLoadingDoctor] = useState(true);
    const [booking, setBooking] = useState(false);
    const [error, setError] = useState("");
    const [success, setSuccess] = useState(null);

    useEffect(() => {
        loadDoctor();
    }, [doctorId]);

    const loadDoctor = async () => {
        try {
            setLoadingDoctor(true);
            setError("");

            const data = await doctorService.getDoctorById(doctorId);
            setDoctor(data);
        } catch (error) {
            setError(error.response?.data?.message || 
            "Unable to load doctor");
        } finally {
            setLoadingDoctor(false);
        }
    };

    const getToday = () => {
        const today = new Date();
        const year = today.getFullYear();
        const month = String(today.getMonth() + 1).padStart(2, "0");
        const day = String(today.getDate()).padStart(2, "0");

        return `${year}-${month}-${day}`;
    };

    const handleSubmit = async (event) => {
        event.preventDefault();
        setError("");
        setSuccess(null);

        if (!appointmentDate) {
            setError("Please select an appointment date");
            return;
        }

        if (!timeSlot) {
            setError("Please select a time slot");
            return;
        }

        try {
            setBooking(true);

            const appointmentData = {
                doctorId: Number(doctorId),
                appointmentDate,
                timeSlot
            };

            const response = 
               await appointmentService.createAppointment(appointmentData);
            setSuccess(response);
            setAppointmentDate("");
            setTimeSlot("");
        } catch (error) {
            setError(error.response?.data?.message || 
              "Unable to book appointment");
        } finally {
            setBooking(false);
        }
    };

    if (loadingDoctor) {
        return (
            <div className="container py-5 text-center">
                <div className="spinner-border text-primary"></div>
                <p className="text-secondary mt-3">Loading doctor...</p>
            </div>
        );
    }

    if (!doctor) {
        return (
            <div className="container py-5">
                <div className="alert alert-danger">
                    Doctor information could not be loaded.
                </div>
                <Link to="/patient/doctors" className="btn btn-primary">
                    Back to Doctors
                </Link>
            </div>
        );
    }

    return (
        <div className="container py-5">
            <div className="row justify-content-center">
                <div className="col-lg-8">
                    {/* PAGE HEADER */}
                    <div className="mb-4">
                        <h2>Book Appointment</h2>
                        <p className="text-secondary">
                            Select your preferred appointment date and time.
                        </p>
                    </div>

                    {/* DOCTOR INFORMATION */}
                    <div className="card shadow-sm mb-4">
                        <div className="card-body">
                            <div className="row align-items-center">
                                {doctor.photoUrl && (
                                    <div className="col-md-3 mb-3 mb-md-0">
                                        <img src={doctor.photoUrl} alt={doctor.name} 
                                             className="img-fluid rounded" />
                                    </div>
                                )}

                                <div className={doctor.photoUrl ? "col-md-9" : "col-12"}>
                                    <h4 className="mb-1">{doctor.name}</h4>
                                    <p className="text-primary fw-semibold mb-2">
                                        {doctor.specialization}
                                    </p>
                                    <p className="mb-1">
                                        <strong>Experience:</strong> 
                                      {doctor.experienceYears} years
                                    </p>
                                    <p className="mb-0">
                                        <strong>Consultation Fee:</strong> 
                                      ₹{doctor.consultationFee}
                                    </p>
                                </div>
                            </div>
                        </div>
                    </div>

                    {/* ERROR */}
                    {error && (
                        <div className="alert alert-danger">{error}</div>
                    )}

                    {/* SUCCESS */}
                    {success && (
                        <div className="alert alert-success">
                            <h5>Appointment Booked</h5>
                            <p className="mb-1">
                                Appointment ID: <strong>{success.id}</strong>
                            </p>
                            <p className="mb-1">
                                Doctor: <strong>{success.doctorName}</strong>
                            </p>
                            <p className="mb-1">
                                Date: <strong>{success.appointmentDate}</strong>
                            </p>
                            <p className="mb-1">
                                Slot: <strong>{success.timeSlot}</strong>
                            </p>
                            <p className="mb-1">
                                Status: <strong>{success.appointmentStatus}</strong>
                            </p>
                            <p className="mb-0">
                                Payment: <strong>{success.paymentStatus}</strong>
                            </p>
                        </div>
                    )}

                    {/* BOOKING FORM */}
                    <div className="card shadow-sm">
                        <div className="card-body p-4">
                            <form onSubmit={handleSubmit}>
                                {/* DATE */}
                                <div className="mb-4">
                                    <label className="form-label">
                                      Appointment Date</label>
                                    <input
                                        type="date"
                                        className="form-control"
                                        min={getToday()}
                                        value={appointmentDate}
                                        onChange={(event) => 
                                           setAppointmentDate(event.target.value)}
                                        required
                                    />
                                </div>

                                {/* TIME SLOT */}
                                <div className="mb-4">
                                    <label className="form-label">
                                      Select Time Slot</label>
                                    <div className="row g-3">
                                        {/* 10 AM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotMorning"
                                                value="MORNING_10_AM"
                                                checked={timeSlot === "MORNING_10_AM"}
                                                onChange={(event) => 
                                           setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary w-100" 
                                                   htmlFor="slotMorning">
                                                10:00 AM
                                            </label>
                                        </div>

                                        {/* 2 PM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotAfternoon"
                                                value="AFTERNOON_2_PM"
                                                checked={timeSlot === "AFTERNOON_2_PM"}
                                                onChange={(event) => 
                                                   setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary 
                                                   w-100" htmlFor="slotAfternoon">
                                                2:00 PM
                                            </label>
                                        </div>

                                        {/* 5 PM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotEvening"
                                                value="EVENING_5_PM"
                                                checked={timeSlot === "EVENING_5_PM"}
                                                onChange={(event) => 
                                                    setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary 
                                              w-100" htmlFor="slotEvening">
                                                5:00 PM
                                            </label>
                                        </div>
                                    </div>
                                </div>

                                {/* FEE */}
                                <div className="alert alert-light border">
                                    Consultation Fee: <strong>
                                        ₹{doctor.consultationFee}</strong>
                                </div>

                                <div className="d-flex gap-2">
                                    <Link to="/patient/doctors" 
                                   className="btn btn-outline-secondary">
                                        Back
                                    </Link>
                                    <button
                                        type="submit"
                                        className="btn btn-primary flex-grow-1"
                                        disabled={booking}
                                    >
                                        {booking ? "Booking..." : "Book Appointment"}
                                    </button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default BookAppointment;

    const getToday = () => {
        const today = new Date();
        const year = today.getFullYear();
        const month = String(today.getMonth() + 1).padStart(2, "0");
        const day = String(today.getDate()).padStart(2, "0");

        return `${year}-${month}-${day}`;
    };

    const handleSubmit = async (event) => {
        event.preventDefault();
        setError("");
        setSuccess(null);

        if (!appointmentDate) {
            setError("Please select an appointment date");
            return;
        }

        if (!timeSlot) {
            setError("Please select a time slot");
            return;
        }

        try {
            setBooking(true);
            const appointmentData = {
                doctorId: Number(doctorId),appointmentDate,timeSlot
            };

            const response = 
               await appointmentService.createAppointment(appointmentData);
            setSuccess(response);
            setAppointmentDate("");
            setTimeSlot("");
        } catch (error) {
            setError(error.response?.data?.message || 
              "Unable to book appointment");
        } finally {
            setBooking(false);
        }
    };

    if (loadingDoctor) {
        return (
            <div className="container py-5 text-center">
                <div className="spinner-border text-primary"></div>
                <p className="text-secondary mt-3">Loading doctor...</p>
            </div>
        );
    }

    if (!doctor) {
        return (
            <div className="container py-5">
                <div className="alert alert-danger">
                    Doctor information could not be loaded.
                </div>
                <Link to="/patient/doctors" className="btn btn-primary">
                    Back to Doctors
                </Link>
            </div>
        );
    }

    return (
        <div className="container py-5">
            <div className="row justify-content-center">
                <div className="col-lg-8">
                    {/* PAGE HEADER */}
                    <div className="mb-4">
                        <h2>Book Appointment</h2>
                        <p className="text-secondary">
                            Select your preferred appointment date and time.
                        </p>
                    </div>

                    {/* DOCTOR INFORMATION */}
                    <div className="card shadow-sm mb-4">
                        <div className="card-body">
                            <div className="row align-items-center">
                                {doctor.photoUrl && (
                                    <div className="col-md-3 mb-3 mb-md-0">
                                        <img src={doctor.photoUrl} alt={doctor.name} 
                                             className="img-fluid rounded" />
                                    </div>
                                )}

                                <div className={doctor.photoUrl ? "col-md-9" : "col-12"}>
                                    <h4 className="mb-1">{doctor.name}</h4>
                                    <p className="text-primary fw-semibold mb-2">
                                        {doctor.specialization}
                                    </p>
                                    <p className="mb-1">
                                        <strong>Experience:</strong> 
                                      {doctor.experienceYears} years
                                    </p>
                                    <p className="mb-0">
                                        <strong>Consultation Fee:</strong> 
                                      ₹{doctor.consultationFee}
                                    </p>
                                </div>
                            </div>
                        </div>
                    </div>

                    {/* ERROR */}
                    {error && (
                        <div className="alert alert-danger">{error}</div>
                    )}

                    {/* SUCCESS */}
                    {success && (
                        <div className="alert alert-success">
                            <h5>Appointment Booked</h5>
                            <p className="mb-1">
                                Appointment ID: <strong>{success.id}</strong>
                            </p>
                            <p className="mb-1">
                                Doctor: <strong>{success.doctorName}</strong>
                            </p>
                            <p className="mb-1">
                                Date: <strong>{success.appointmentDate}</strong>
                            </p>
                            <p className="mb-1">
                                Slot: <strong>{success.timeSlot}</strong>
                            </p>
                            <p className="mb-1">
                                Status: <strong>{success.appointmentStatus}</strong>
                            </p>
                            <p className="mb-0">
                                Payment: <strong>{success.paymentStatus}</strong>
                            </p>
                        </div>
                    )}

                    {/* BOOKING FORM */}
                    <div className="card shadow-sm">
                        <div className="card-body p-4">
                            <form onSubmit={handleSubmit}>
                                {/* DATE */}
                                <div className="mb-4">
                                    <label className="form-label">
                                      Appointment Date</label>
                                    <input
                                        type="date"
                                        className="form-control"
                                        min={getToday()}
                                        value={appointmentDate}
                                        onChange={(event) => 
                                           setAppointmentDate(event.target.value)}
                                        required
                                    />
                                </div>

                                {/* TIME SLOT */}
                                <div className="mb-4">
                                    <label className="form-label">
                                      Select Time Slot</label>
                                    <div className="row g-3">
                                        {/* 10 AM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotMorning"
                                                value="MORNING_10_AM"
                                                checked={timeSlot === "MORNING_10_AM"}
                                                onChange={(event) => 
                                           setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary w-100" 
                                                   htmlFor="slotMorning">
                                                10:00 AM
                                            </label>
                                        </div>

                                        {/* 2 PM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotAfternoon"
                                                value="AFTERNOON_2_PM"
                                                checked={timeSlot === "AFTERNOON_2_PM"}
                                                onChange={(event) => 
                                                   setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary 
                                                   w-100" htmlFor="slotAfternoon">
                                                2:00 PM
                                            </label>
                                        </div>

                                        {/* 5 PM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotEvening"
                                                value="EVENING_5_PM"
                                                checked={timeSlot === "EVENING_5_PM"}
                                                onChange={(event) => 
                                                    setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary 
                                              w-100" htmlFor="slotEvening">
                                                5:00 PM
                                            </label>
                                        </div>
                                    </div>
                                </div>

                                {/* FEE */}
                                <div className="alert alert-light border">
                                    Consultation Fee: <strong>
                                        ₹{doctor.consultationFee}</strong>
                                </div>

                                <div className="d-flex gap-2">
                                    <Link to="/patient/doctors" 
                                   className="btn btn-outline-secondary">
                                        Back
                                    </Link>
                                    <button
                                        type="submit"
                                        className="btn btn-primary flex-grow-1"
                                        disabled={booking}
                                    >
                                        {booking ? "Booking..." : "Book Appointment"}
                                    </button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default BookAppointment;

            const response = 
               await appointmentService.createAppointment(appointmentData);
            setSuccess(response);
            setAppointmentDate("");
            setTimeSlot("");
        } catch (error) {
            setError(error.response?.data?.message || 
              "Unable to book appointment");
        } finally {
            setBooking(false);
        }
    };

    if (loadingDoctor) {
        return (
            <div className="container py-5 text-center">
                <div className="spinner-border text-primary"></div>
                <p className="text-secondary mt-3">Loading doctor...</p>
            </div>
        );
    }

    if (!doctor) {
        return (
            <div className="container py-5">
                <div className="alert alert-danger">
                    Doctor information could not be loaded.
                </div>
                <Link to="/patient/doctors" className="btn btn-primary">

                    Back to Doctors
                </Link>
            </div>
        );
    }

    return (
        <div className="container py-5">
            <div className="row justify-content-center">
                <div className="col-lg-8">
                    {/* PAGE HEADER */}
                    <div className="mb-4">
                        <h2>Book Appointment</h2>
                        <p className="text-secondary">
                            Select your preferred appointment date and time.
                        </p>
                    </div>

                    {/* DOCTOR INFORMATION */}
                    <div className="card shadow-sm mb-4">
                        <div className="card-body">
                            <div className="row align-items-center">
                                {doctor.photoUrl && (
                                    <div className="col-md-3 mb-3 mb-md-0">
                                        <img src={doctor.photoUrl} alt={doctor.name} 
                                             className="img-fluid rounded" />
                                    </div>
                                )}

                                <div className={doctor.photoUrl ? 
                                                "col-md-9" : "col-12"}>
                                    <h4 className="mb-1">
                                      {doctor.name}</h4>
                                    <p className="text-primary 
                                         fw-semibold mb-2">
                                        {doctor.specialization}
                                    </p>
                                    <p className="mb-1">
                                        <strong>Experience:</strong> 
                                      {doctor.experienceYears} years
                                    </p>
                                    <p className="mb-0">
                                        <strong>
                                        Consultation Fee:</strong> 
                                      ₹{doctor.consultationFee}
                                    </p>
                                </div>
                            </div>
                        </div>
                    </div>

                    {/* ERROR */}
                    {error && (
                        <div className="alert alert-danger">
                      {error}</div>
                    )}

                    {/* SUCCESS */}
                    {success && (
                        <div className="alert alert-success">
                            <h5>Appointment Booked</h5>
                            <p className="mb-1">
                                Appointment ID: <strong>
                      {success.id}</strong>
                            </p>
                            <p className="mb-1">
                                Doctor: <strong>{success.doctorName}</strong>
                            </p>
                            <p className="mb-1">
                                Date: <strong>{success.appointmentDate}</strong>
                            </p>
                            <p className="mb-1">
                                Slot: <strong>{success.timeSlot}</strong>
                            </p>
                            <p className="mb-1">
                                Status: <strong>{success.appointmentStatus}</strong>
                            </p>
                            <p className="mb-0">
                                Payment: <strong>{success.paymentStatus}</strong>
                            </p>
                        </div>
                    )}

                    {/* BOOKING FORM */}
                    <div className="card shadow-sm">
                        <div className="card-body p-4">

                            <form onSubmit={handleSubmit}>
                                {/* DATE */}
                                <div className="mb-4">
                                    <label className="form-label">
                                      Appointment Date</label>
                                    <input
                                        type="date"
                                        className="form-control"
                                        min={getToday()}
                                        value={appointmentDate}
                                        onChange={(event) => 
                                           setAppointmentDate(event.target.value)}
                                        required
                                    />
                                </div>

                                {/* TIME SLOT */}
                                <div className="mb-4">
                                    <label className="form-label">
                                      Select Time Slot</label>
                                    <div className="row g-3">
                                        {/* 10 AM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotMorning"
                                                value="MORNING_10_AM"

                                                onChange={(event) => 
                                           setTimeSlot(event.target.value)}
                                            />
                                            <label 
                                            className="btn btn-outline-primary w-100" 
                                            htmlFor="slotMorning">
                                                10:00 AM
                                            </label>
                                        </div>

                                        {/* 2 PM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotAfternoon"
                                                value="AFTERNOON_2_PM"
                                                checked={timeSlot === "AFTERNOON_2_PM"}
                                                onChange={(event) => 
                                                   setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary 
                                                   w-100" htmlFor="slotAfternoon">
                                                2:00 PM
                                            </label>
                                        </div>

                                        {/* 5 PM */}
                                        <div className="col-md-4">
                                            <input
                                                type="radio"
                                                className="btn-check"
                                                name="timeSlot"
                                                id="slotEvening"
                                                value="EVENING_5_PM"
                                                checked={timeSlot === "EVENING_5_PM"}
                                                onChange={(event) => 
                                                    setTimeSlot(event.target.value)}
                                            />
                                            <label className="btn btn-outline-primary 
                                              w-100" htmlFor="slotEvening">
                                                5:00 PM
                                            </label>
                                        </div>
                                    </div>
                                </div>

                                {/* FEE */}
                                <div className="alert alert-light border">
                                    Consultation Fee: <strong>
                                        ₹{doctor.consultationFee}</strong>
                                </div>

                                <div className="d-flex gap-2">
                                    <Link to="/patient/doctors" 
                                   className="btn btn-outline-secondary">

                                        Back
                                    </Link>
                                    <button
                                        type="submit"
                                        className="btn btn-primary flex-grow-1"
                                        disabled={booking}>
                                        {booking ? "Booking..." : "Book Appointment"}
                                    </button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}
export default BookAppointment;

Text

  • The complete code file attached below contains the same code as provided above, with proper indentation and formatting for better readability.

Why the Backend Receives Enum Values

  • The user sees:
10:00 AM
2:00 PM
5:00 PM
  • But React sends:
MORNING_10_AM
AFTERNOON_2_PM
EVENING_5_PM
  • Because our Spring enum is:
public enum TimeSlot {
    MORNING_10_AM,
    AFTERNOON_2_PM,
    EVENING_5_PM
}
  • So:
Human-friendly UI
       ↓
Enum value
       ↓
Spring Boot
  • This is better than exposing ugly enum names to the user.

Why We Don't Send Patient ID

  • Our request becomes:
{
    "doctorId": 3,
    "appointmentDate": "2026-09-26",
    "timeSlot": "MORNING_10_AM"
}
  • Notice :
patientId ❌
consultationFee ❌
appointmentStatus ❌
paymentStatus ❌
  • The backend gets the patient from:
JWT
 ↓
email
 ↓
User
  • And consultation fee from: DoctorProfile

Update Router

4

  • Open src/routes/router.jsx
  • Add:
import BookAppointment from "../pages/patient/BookAppointment";
  • Then inside MainLayout → children, add:
{
    path: "patient/doctors/:doctorId/book",

    element: (
        <ProtectedRoute allowedRoles={[
                "PATIENT"
            ]} >
            <BookAppointment />
        </ProtectedRoute>
    )
},
  • So now: /patient/doctors contains doctor cards.
  • Clicking doctor 3 goes to: /patient/doctors/3/book
  • And: useParams() extracts: doctorId = "3"

Test Doctor Navigation

5

  • Login as PATIENT.
Doctor Name
Specialization
Experience
Fee
  • Open: /patient/doctors
  • Click: Book Appointment
  • Suppose doctor ID is 3.
  • You should reach: /patient/doctors/3/book
  • The frontend calls: GET /api/doctors/3 and displays:

Test Appointment Booking

6

Date: September 26
Slot: 10:00 AM
  • Select:
  • Frontend sends:
POST /api/appointments
Authorization: Bearer <patient-token>
Content-Type: application/json
  • Body:
{
    "doctorId": 3,
    "appointmentDate": "2026-09-26",
    "timeSlot": "MORNING_10_AM"
}
patient → from JWT
doctor → ID 3
consultationFee → DoctorProfile
appointmentStatus → PENDING
paymentStatus → UNPAID
  • Backend should create:
  • Expected: 201 CREATED
  • Expected Response
{
    "id": 7,
    "patientId": 1,
    "patientName": "Rahul Shah",
    "doctorId": 3,
    "doctorName": "Dr. Ananya Sharma",
    "specialization": "Cardiologist",
    "appointmentDate": "2026-09-26",
    "timeSlot": "MORNING_10_AM",
    "consultationFee": 1200,
    "appointmentStatus": "PENDING",
    "paymentStatus": "UNPAID",
    "createdAt": "2026-09-24T18:10:00"
}

Duplicate Slot Test

7

  • Suppose this already exists:
Doctor: Dr. Ananya
Date: 2026-09-26
Slot: 10 AM
  • Try booking it again.
Doctor 3
+
2026-09-26
+
MORNING_10_AM
  • Backend should detect:
  • And return: 409 CONFLICT
  • Something like: Selected appointment slot is already booked

Test Another Slot

8

  • Same doctor: September 26 but choose: 2:00 PM
{
    "doctorId": 3,
    "appointmentDate": "2026-09-26",
    "timeSlot": "AFTERNOON_2_PM"
}
  • Request:
  • This should succeed if that slot is free.

Test Another Doctor

9

  • If: Dr. Ananya 10 AM is already booked, another doctor can still have:
Dr. Ananya
10 AM

Cancelled Slot Behavior

11

  • Because slot availability is based on:
Doctor
+
Date
+
Slot
  • Not globally across the hospital
  • Our backend query intentionally ignores: CANCELLED  appointments when checking
    slot occupancy
  • So:
Appointment
Doctor 3
10 AM
CANCELLED
  • Means the same slot becomes available again.

We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below: 

 

Great job!

You have successfully completed the appointment booking functionality in the Medicare application.

Checkpoint

   Git Push

git push origin branchName

Next-Lab Preparation

Module:

1) React Basics & Backend Integration (Spring Boot)​

SpringBoot Lab - 17

By Content ITV

SpringBoot Lab - 17

  • 50