From 649d5c692e0e6430bc6a7c8b7f1f1b6ba2b5cf81 Mon Sep 17 00:00:00 2001 From: plnsliyanage Date: Mon, 16 Feb 2026 10:29:05 +0530 Subject: [PATCH 1/3] add drop down for specaility selection --- .../bookings/BookingDoctorSelect.jsx | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/components/PatientPortal/bookings/BookingDoctorSelect.jsx b/src/components/PatientPortal/bookings/BookingDoctorSelect.jsx index 571f7a3..d8152aa 100644 --- a/src/components/PatientPortal/bookings/BookingDoctorSelect.jsx +++ b/src/components/PatientPortal/bookings/BookingDoctorSelect.jsx @@ -3,41 +3,78 @@ import { getDoctors } from "../../../api/api"; export default function DoctorSelect({ selectedDoctor, setSelectedDoctor }) { const [doctors, setDoctors] = useState([]); + const [specialties, setSpecialties] = useState([]); + const [selectedSpecialty, setSelectedSpecialty] = useState(""); useEffect(() => { getDoctors() - .then((data) => setDoctors(data)) - .catch(console.error); + .then((data) => { + setDoctors(data || []); + + // Extract unique specialties + const uniqueSpecialties = [ + ...new Set((data || []).map((d) => d.specialty)), + ]; + setSpecialties(uniqueSpecialties); + }) + .catch((err) => console.error("Failed to load doctors:", err)); }, []); - const handleChange = (e) => { + // Filter doctors based on selected specialty + const filteredDoctors = doctors.filter( + (d) => d.specialty === selectedSpecialty, + ); + + const handleSpecialtyChange = (e) => { + setSelectedSpecialty(e.target.value); + setSelectedDoctor(null); // reset doctor when specialty changes + }; + + const handleDoctorChange = (e) => { const doctor = doctors.find((d) => d.id === Number(e.target.value)); setSelectedDoctor(doctor || null); }; return (
+ {/* SPECIALTY DROPDOWN */} + + + {/* DOCTOR DROPDOWN */} + + {/* SHOW CONSULTATION FEE */} {selectedDoctor && ( -
-

- Specialty: {selectedDoctor.specialty} -

+

Consultation Fee: Rs. {selectedDoctor.consultationFee} From 55a07016e4831e640c064eeb76281628bc413aa7 Mon Sep 17 00:00:00 2001 From: plnsliyanage Date: Tue, 3 Mar 2026 16:19:41 +0530 Subject: [PATCH 2/3] done untile available slot part --- src/api/api.js | 29 ++--- .../PatientPortal/bookings/BookingPage.jsx | 4 +- .../bookings/DoctorAvailabilityModal.jsx | 96 ++++++++++++----- .../bookings/PatientAppointmentCard.jsx | 90 +++++++++++++--- .../PatientPortal/bookings/RescheduleCart.jsx | 101 ++++++++++++++++++ 5 files changed, 261 insertions(+), 59 deletions(-) create mode 100644 src/components/PatientPortal/bookings/RescheduleCart.jsx diff --git a/src/api/api.js b/src/api/api.js index 6a5b95a..1526034 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -47,6 +47,14 @@ export const getAvailableSlots = async (doctorId, date) => { return res.data; }; +export const getAllSlots = async (doctorId, date) => { + const res = await API.get(`/availability/doctor/${doctorId}/all`, { + params: { date }, + }); + return res.data; +}; + + export const saveAvailability = async ({ doctorId, date, times }) => { const res = await API.post("/availability", { doctorId, @@ -70,49 +78,42 @@ export const deleteAvailabilitySlot = async (id) => { APPOINTMENTS ========================= */ export const bookAppointment = async (data) => { - // POST /api/appointments/book const res = await API.post("/appointments/book", data); return res.data; }; export const getAppointmentTypes = async () => { - // GET /api/appointment-types const res = await API.get("/appointment-types"); return res.data; }; -// This matches your current backend controller: -// GET /api/appointments/user/success +// GET appointments for logged-in patient export const getUserAppointments = async () => { try { - const res = await API.get("/appointments/user/success"); - return res.data; + const response = await API.get("/appointments/user/success"); + return response.data; } catch (err) { - console.error("Error fetching appointments:", err); - return []; + console.error("Failed to fetch user appointments:", err); + throw err; } }; /* ========================= ADMIN – APPOINTMENTS ========================= */ - -// This matches AdminAppointmentController: -// GET /api/admin/appointments export const getAllAppointments = async () => { const res = await API.get("/admin/appointments"); return res.data; }; -// This matches AdminAppointmentController: -// POST /api/admin/appointments/confirm/{id}?physicalLocation=... OR ?zoomLink=... export const confirmAppointment = async (appointmentId, params) => { const res = await API.post( `/admin/appointments/confirm/${appointmentId}`, null, - { params } // axios will convert this to query string + { params } ); return res.data; }; + export default API; diff --git a/src/components/PatientPortal/bookings/BookingPage.jsx b/src/components/PatientPortal/bookings/BookingPage.jsx index f8795ae..84d91b2 100644 --- a/src/components/PatientPortal/bookings/BookingPage.jsx +++ b/src/components/PatientPortal/bookings/BookingPage.jsx @@ -10,7 +10,7 @@ export default function BookingsTab() { const fetchAppointments = async () => { try { const data = await getUserAppointments(); - setAppointments(data); // set state + setAppointments(data); // only logged-in patient’s appointments } catch (err) { console.error("Error fetching appointments:", err); } @@ -21,7 +21,7 @@ export default function BookingsTab() { }, []); const handleBookingSuccess = (appointment) => { - // Add new appointment to list + // Add new appointment to list (optional: you could re-fetch from backend) setAppointments((prev) => [...prev, appointment]); }; diff --git a/src/components/PatientPortal/bookings/DoctorAvailabilityModal.jsx b/src/components/PatientPortal/bookings/DoctorAvailabilityModal.jsx index fab34c0..cbcd8f5 100644 --- a/src/components/PatientPortal/bookings/DoctorAvailabilityModal.jsx +++ b/src/components/PatientPortal/bookings/DoctorAvailabilityModal.jsx @@ -1,15 +1,15 @@ import React, { useEffect, useState } from "react"; import { saveAvailability, - getAvailableSlots, + getAllSlots, deleteAvailabilitySlot, } from "../../../api/api"; // Generate 30-min slots between 8:00 AM to 11:00 PM const generateSlots = () => { const slots = []; - let start = 8 * 60; // 8:00 AM in minutes - const end = 23 * 60; // 11:00 PM in minutes + let start = 8 * 60; // 8:00 AM + const end = 23 * 60; // 11:00 PM while (start <= end) { const h = String(Math.floor(start / 60)).padStart(2, "0"); @@ -21,7 +21,7 @@ const generateSlots = () => { return slots; }; -// Helper to normalize backend time (remove seconds if present) +// Normalize backend time (remove seconds) const normalizeTime = (time) => time.slice(0, 5); export default function DoctorAvailabilityModal({ doctor, onClose }) { @@ -30,19 +30,19 @@ export default function DoctorAvailabilityModal({ doctor, onClose }) { const [selectedSlots, setSelectedSlots] = useState([]); const [savedSlots, setSavedSlots] = useState([]); - // Toggle slot selection + // Toggle selection (only for unsaved slots) const toggleSlot = (time) => { setSelectedSlots((prev) => prev.includes(time) ? prev.filter((t) => t !== time) : [...prev, time], ); }; - // Load saved slots from backend + // Load all slots (booked + available) const loadSavedSlots = async () => { if (!doctor?.id || !date) return; try { - const res = await getAvailableSlots(doctor.id, date); + const res = await getAllSlots(doctor.id, date); const normalized = res.map((s) => ({ ...s, availableTime: normalizeTime(s.availableTime), @@ -76,7 +76,7 @@ export default function DoctorAvailabilityModal({ doctor, onClose }) { } }; - // Delete a slot + // Delete slot (only allowed if not booked) const handleDelete = async (slotId) => { if (!window.confirm("Are you sure you want to delete this slot?")) return; @@ -90,15 +90,31 @@ export default function DoctorAvailabilityModal({ doctor, onClose }) { } }; - // Load slots whenever date or doctor changes useEffect(() => { + setSelectedSlots([]); // Clear temporary selection when date changes loadSavedSlots(); }, [doctor, date]); + // Merge selected slots into table preview + const mergedSlots = [ + ...savedSlots, + ...selectedSlots + .filter((time) => !savedSlots.some((s) => s.availableTime === time)) + .map((time) => ({ + id: "temp-" + time, + availableDate: date, + availableTime: time, + isBooked: false, + isTemp: true, // optional flag for temporary + })), + ]; + return (

-

Availability – {doctor.name}

+

+ Availability – {doctor?.name} +

{/* DATE PICKER */} setDate(e.target.value)} /> - {/* TIME SLOTS */} + {/* TIME SLOTS GRID */}
{slots.map((time) => { + const slotObj = mergedSlots.find((s) => s.availableTime === time); + const isSaved = !!slotObj && !slotObj.isTemp; + const isBooked = slotObj?.isBooked; const isSelected = selectedSlots.includes(time); - const isSaved = savedSlots.some((s) => s.availableTime === time); return ( @@ -131,35 +152,51 @@ export default function DoctorAvailabilityModal({ doctor, onClose }) { })}
- {/* SAVED SLOTS TABLE */} + {/* SAVED + SELECTED SLOTS TABLE */}

Saved Slots

+ - {savedSlots.length > 0 ? ( - savedSlots.map((s) => ( + {mergedSlots.length > 0 ? ( + mergedSlots.map((s) => ( + )) ) : ( - @@ -172,6 +209,7 @@ export default function DoctorAvailabilityModal({ doctor, onClose }) { + + + + + {/* Cancel Confirmation */} + {cancelConfirm && ( +
+

+ We understand plans can change. You can reschedule your appointment + at no additional cost. Cancelling the appointment will not refund + your payment. +

+
+ + +
+
+ )} + + {/* Reschedule Cart */} + {rescheduleMode && ( + setRescheduleMode(false)} + /> )} ); diff --git a/src/components/PatientPortal/bookings/RescheduleCart.jsx b/src/components/PatientPortal/bookings/RescheduleCart.jsx new file mode 100644 index 0000000..927fed2 --- /dev/null +++ b/src/components/PatientPortal/bookings/RescheduleCart.jsx @@ -0,0 +1,101 @@ +import { useState } from "react"; + +export default function RescheduleCart({ + appointment, + availableSlots, + onSubmit, + onCancel, +}) { + const [bookingDate, setBookingDate] = useState(appointment.bookingDate); + const [bookingTime, setBookingTime] = useState(appointment.bookingTime); + const [reason, setReason] = useState(appointment.reason || ""); + + // Get available times for the selected date + const timesForDate = + availableSlots.find((slot) => slot.date === bookingDate)?.times || []; + + const handleSubmit = (e) => { + e.preventDefault(); + onSubmit({ + appointmentId: appointment.appointmentId, + bookingDate, + bookingTime, + reason, + }); + }; + + return ( +
+

Reschedule Appointment

+ + {/* Read-only fields */} +

+ Doctor: {appointment.doctorName} +

+

+ Specialty: {appointment.specialty} +

+

+ Payment Amount: Rs.{appointment.consultationFee} +

+ + {/* Editable fields */} +
+ +
Date TimeStatus Actions
{s.availableDate} {s.availableTime} - + {s.isBooked ? ( + Booked + ) : s.isTemp ? ( + + Selected + + ) : ( + + Available + + )} + + {!s.isBooked && !s.isTemp && ( + + )}
+ No availability saved