Business Scenario
In this lab, you will enhance the Medicare application by implementing the Doctor Schedule feature, allowing doctors to view and manage their assigned patient appointments.
Pre-Lab Preparation
Module:
1) React Basics & Backend Integration (Spring Boot)
Phase 12G — Doctor Schedule & Complete Appointment
DOCTOR Login ↓ Doctor Dashboard ↓ My Schedule
↓
GET /api/appointments/doctor/me
↓
Assigned Appointments
↓
Patient Name
Date
Slot
Fee
Status
↓
Mark Completed
↓
PATCH /api/appointments/{id}/complete
↓
COMPLETED
↓
Add PrescriptionUpdate appointmentService.js
1
import api, {getAuthConfig} from "../api/api";
const createAppointment =
async (appointmentData) => {
const response =
await api.post(
"/appointments",
appointmentData,
getAuthConfig()
); return response.data;
};
const getMyAppointments =
async () => {
const response =
await api.get(
"/appointments/my",
getAuthConfig()
);
return response.data;
};
const cancelAppointment =
async (appointmentId) => {
const response =
await api.patch(
`/appointments/${appointmentId}/cancel`,
{},
getAuthConfig()
);
return response.data;
};const getDoctorAppointments =
async () => {
const response =
await api.get(
"/appointments/doctor/me",
getAuthConfig()
);
return response.data;
};
const completeAppointment =
async (appointmentId) => {
const response =
await api.patch(
`/appointments/${appointmentId}/complete`,
{},
getAuthConfig()
);
return response.data;
};
const appointmentService = {
createAppointment,
getMyAppointments, cancelAppointment,
getDoctorAppointments,
completeAppointment
};
export default appointmentService;Create Doctor AppointmentCard
2
src/components/appointment/DoctorAppointmentCard.jsx
import { Link } from "react-router";
function DoctorAppointmentCard({ appointment, onComplete, completingId }) {
const formatTimeSlot = (timeSlot) => {
switch (timeSlot) { case "MORNING_10_AM":
return "10:00 AM";
case "AFTERNOON_2_PM":
return "2:00 PM";
case "EVENING_5_PM":
return "5:00 PM";
default:
return timeSlot;
}
};
const getStatusBadge = (status) => {
switch (status) {
case "PENDING":
return "text-bg-warning";
case "CONFIRMED":
return "text-bg-primary";
case "COMPLETED":
return "text-bg-success";
case "CANCELLED":
return "text-bg-secondary";
default:
return "text-bg-light";
}
};
const canComplete =
appointment.appointmentStatus !== "COMPLETED" &&
appointment.appointmentStatus !== "CANCELLED"; return (
<div className="card shadow-sm h-100">
<div className="card-body">
<div className="d-flex justify-content-between
align-items-start mb-3">
<div>
<h5 className="mb-1">
{appointment.patientName}
</h5>
<small className="text-secondary">
Patient
</small>
</div>
<span
className={`badge ${getStatusBadge(
appointment.appointmentStatus
)}`}
>
{appointment.appointmentStatus}
</span>
</div>
<hr />
<p className="mb-2">
<strong>Appointment ID:</strong> {appointment.id}
</p>
<p className="mb-2"> <strong>Date:</strong> {appointment.appointmentDate}
</p>
<p className="mb-2">
<strong>Time:</strong> {formatTimeSlot(appointment.timeSlot)}
</p>
<p className="mb-2">
<strong>Consultation Fee:</strong> ₹{appointment.consultationFee}
</p>
<p className="mb-4">
<strong>Payment:</strong> {appointment.paymentStatus}
</p>
{canComplete && (
<button
className="btn btn-success w-100"
onClick={() => onComplete(appointment.id)}
disabled={completingId === appointment.id}
>
{completingId === appointment.id
? "Completing..."
: "Mark Completed"}
</button>
)}
{appointment.appointmentStatus === "COMPLETED" && (
<Link to={`/doctor/appointments/${appointment.id}/prescription`}
className="btn btn-primary w-100"> Add Prescription
</Link>
)}
{appointment.appointmentStatus === "CANCELLED" && (
<div className="alert alert-secondary mb-0">
Patient cancelled this appointment.
</div>
)}
</div>
</div>
);
}
export default DoctorAppointmentCard;Why Add Prescription Already Links Somewhere
<Link to={`/doctor/appointments/${appointment.id}/prescription` }>
Add Prescription
</Link><button className="btn btn-primary w-100" disabled>
Add Prescription — Coming Soon
</button>{
appointment .appointmentStatus ===
"COMPLETED"
&& (
<button
className="btn btn-primary w-100"
disabled
>
Add Prescription
— Coming Soon
</button>
)
}Create Doctor Schedule Page
3
src/pages/doctor/DoctorSchedule.jsx
import { useEffect, useState} from "react";
import appointmentService from "../../services/appointmentService";
import DoctorAppointmentCard
from "../../components/appointment/DoctorAppointmentCard";
function DoctorSchedule() {
const [appointments, setAppointments] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [completingId, setCompletingId] = useState(null);
useEffect(() => {
loadAppointments();
}, []);
const loadAppointments = async () => {
try {
setLoading(true);
setError("");
const data = await appointmentService
.getDoctorAppointments();
setAppointments(data); } catch (error) {
setError(
error.response
?.data
?.message
||
"Unable to load appointments"
);
} finally {
setLoading(false);
}
};
const handleComplete = async (appointmentId) => {
const confirmed = window.confirm(
"Mark this appointment as completed?"
);
if (!confirmed) {
return;
}
try {
setCompletingId(appointmentId);
setError("");
const updatedAppointment =
await appointmentService
.completeAppointment(appointmentId); setAppointments(
previousAppointments =>
previousAppointments.map(
appointment => {
if (
appointment.id ===
appointmentId
) {
return updatedAppointment;
}
return appointment;
}
)
);
} catch (error) {
setError(
error.response
?.data
?.message
||
"Unable to complete appointment"
);
} finally {
setCompletingId(null);
}
}; if (loading) {
return (
<div className="container py-5 text-center">
<div
className="
spinner-border
text-primary
"
>
</div>
<p className="text-secondary mt-3">
Loading schedule...
</p>
</div>
);
}
return (
<div className="container py-5">
<div className="mb-4">
<h2>
My Schedule
</h2>
<p className="text-secondary">
View and manage your
assigned appointments.
</p>
</div> {error && (
<div className="alert alert-danger">
{error}
</div>
)}
{
appointments.length === 0
? (
<div className="alert alert-info">
You do not have any
appointments assigned.
</div>
)
: (
<div className="row g-4">
{
appointments.map(
appointment => (
<div className="col-md-6 col-lg-4"
key={appointment.id}>
<DoctorAppointmentCard
appointment={appointment}
onComplete={handleComplete}
completingId={completingId}
/>
</div>
)
)
} </div>
)
}
</div>
);
}
export default DoctorSchedule;Add Doctor Schedule Route
4
import DoctorSchedule from "../pages/doctor/DoctorSchedule";{
path: "doctor/schedule",
element: (
<ProtectedRoute allowedRoles={[ "DOCTOR"]}>
<DoctorSchedule />
</ProtectedRoute>
)
},/doctor/dashboard /doctor/schedule
Update Doctor Navbar
5
<>
<li className="nav-item">
<Link
className="nav-link" to="/doctor/dashboard">
Dashboard
</Link>
</li>
<li className="nav-item">
<Link
className="nav-link" to="/doctor/schedule">
My Schedule
</Link>
</li>
</>Dashboard My Schedule Logout
Update Doctor Dashboard
6
import {Link} from "react-router";<Link to="/doctor/schedule" className="btn btn-primary">
View Schedule
</Link>Test Doctor Schedule
7
GET /api/appointments/doctor/me Authorization: Bearer <doctor-token>
JWT ↓ Doctor email ↓ User ↓ DoctorProfile ↓ findByDoctorId() ↓ Only this doctor's appointments
Expected Doctor Card
8
{
"id": 8,
"patientId": 4,
"patientName": "Rahul Shah",
"doctorId": 2,
"doctorName": "Dr. Ananya",
"specialization": "Cardiologist",
"appointmentDate": "2026-09-28",
"timeSlot": "AFTERNOON_2_PM",
"consultationFee": 1200,
"appointmentStatus": "PENDING",
"paymentStatus": "UNPAID",
"createdAt": "2026-09-24T19:00:00"
}Rahul Shah Patient Appointment ID: 8 Date: 2026-09-28 Time: 2:00 PM Consultation Fee: ₹1200 Payment: UNPAID [ Mark Completed ]
Complete Appointment
9
Mark this appointment as completed?
PATCH /api/appointments/8/complete Authorization: Bearer <doctor-token>
Authenticated doctor ↓ Doctor owns appointment? ↓
YES ↓ Appointment CANCELLED? ↓ NO ↓ COMPLETED
Wrong Doctor Security
10
GET /api/appointments/doctor/me
PATCH /api/appointments/8/complete
appointment.doctor.id == loggedInDoctor.id
Cancelled Appointments
11
Patient cancelled this appointment.
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 implemented the Doctor Schedule & Appointment Completion feature in the Medicare application.
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Module:
1) React Basics & Backend Integration (Spring Boot)