From 9bd9eada79580480a8575096bad5197f8296bfeb Mon Sep 17 00:00:00 2001 From: Kavindi487 Date: Sun, 22 Mar 2026 14:53:37 +0530 Subject: [PATCH 1/2] booking updated --- src/api/api.js | 10 ++ .../bookings/PatientAppointmentCard.jsx | 169 ++++++++++++------ .../PatientPortal/bookings/PaymentSuccess.jsx | 29 ++- 3 files changed, 148 insertions(+), 60 deletions(-) diff --git a/src/api/api.js b/src/api/api.js index dc604a4..c8e0d9f 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -104,6 +104,16 @@ export const getDoctorAppointments = async (doctorId) => { } }; +export const verifyPaymentSuccess = async (orderId) => { + try { + const res = await API.get(`/payments/success/${orderId}`); + return res.data; + } catch (err) { + console.error("Error verifying payment:", err); + return null; + } +}; + /* ========================= ADMIN – APPOINTMENTS ========================= */ diff --git a/src/components/PatientPortal/bookings/PatientAppointmentCard.jsx b/src/components/PatientPortal/bookings/PatientAppointmentCard.jsx index 67aa9fd..306f52e 100644 --- a/src/components/PatientPortal/bookings/PatientAppointmentCard.jsx +++ b/src/components/PatientPortal/bookings/PatientAppointmentCard.jsx @@ -1,4 +1,15 @@ import { useState } from "react"; +import { + Stethoscope, + Video, + User, + Calendar, + Clock, + Info, + CreditCard, + Trash2, + ExternalLink +} from "lucide-react"; export default function AppointmentCard({ appointment, onRemove }) { const [error, setError] = useState(""); @@ -6,68 +17,122 @@ export default function AppointmentCard({ appointment, onRemove }) { const handleZoomClick = () => { if ( appointment.locationOrLink && - appointment.locationOrLink.startsWith("https://") + appointment.locationOrLink.startsWith("http") ) { - // Open the link in a new tab window.open(appointment.locationOrLink, "_blank"); - setError(""); // Clear any previous error + setError(""); + } else if (appointment.locationOrLink) { + window.open(`https://${appointment.locationOrLink}`, "_blank"); + setError(""); } else { - setError("Zoom link is invalid or not available.Please contact us "); + setError("Zoom link is invalid or not available."); } }; + const isOnline = String(appointment.appointmentType || "").toLowerCase() === "online"; + return ( -
-

{appointment.doctorName}

-

- Specialty: {appointment.specialty} -

-

- Type: {appointment.appointmentType} -

-

- Consultation Fee: Rs.{appointment.consultationFee} -

-

- Date: {appointment.bookingDate} -

-

- Time: {appointment.bookingTime} -

-

- Reason: {appointment.reason} -

-

- Payment Status: {appointment.paymentStatus} -

-

- Appointment Status: {appointment.appointmentStatus} -

+
+ {/* Header: Doctor Info & Type Badge */} +
+
+
+ +
+
+

+ {appointment.doctorName || "Unknown Doctor"} +

+

{appointment.specialty || "General"}

+
+
+ + {/* Online / Physical Badge */} +
+ {isOnline ?
+
+ + {/* Details Section */} +
+ {/* Date & Time Row */} +
+
+
+ {appointment.bookingDate || "—"} +
+
+
+ {appointment.bookingTime ? appointment.bookingTime.slice(0, 5) : "—"} +
+
+ + {/* Reason Row */} +
+
+ REASON: + {appointment.reason || "N/A"} +
+ + {/* Fee Row */} +
+
+ FEE: + Rs. {appointment.consultationFee || "0"} +
+
+ + {/* Status Badges Row */} +
+
+ APPT: {appointment.appointmentStatus || "PENDING"} +
+ +
+ PAY: {appointment.paymentStatus || "PENDING"} +
+
- {/* Zoom Link / Location */} - {appointment.locationOrLink ? ( - - ) : ( -

- Location / Link: N/A -

- )} + {/* Footer: Actions */} +
+ {onRemove ? ( + + ) : ( +
/* Spacer */ + )} - {error &&

{error}

} + {appointment.locationOrLink ? ( + + ) : ( +
+ + Link Pending +
+ )} +
- {onRemove && ( - - )} + {error &&

{error}

}
); } diff --git a/src/components/PatientPortal/bookings/PaymentSuccess.jsx b/src/components/PatientPortal/bookings/PaymentSuccess.jsx index 43dbf00..a538165 100644 --- a/src/components/PatientPortal/bookings/PaymentSuccess.jsx +++ b/src/components/PatientPortal/bookings/PaymentSuccess.jsx @@ -1,16 +1,29 @@ -import { useEffect } from "react"; -import { useNavigate } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { verifyPaymentSuccess } from "../../../api/api.js"; export default function PaymentSuccess() { const nav = useNavigate(); + const [searchParams] = useSearchParams(); + const [status, setStatus] = useState("Verifying payment..."); useEffect(() => { - // The backend already marked it as SUCCESS before redirecting here. - const timer = setTimeout(() => { - nav("/patient-portal"); - }, 2000); - return () => clearTimeout(timer); - }, [nav]); + const verify = async () => { + const orderId = searchParams.get("order_id"); + if (orderId) { + await verifyPaymentSuccess(orderId); + setStatus("Payment Successful! Redirecting..."); + } else { + setStatus("Payment Successful! Redirecting..."); + } + + setTimeout(() => { + nav("/patient-portal"); + }, 2000); + }; + + verify(); + }, [nav, searchParams]); return (
From a1ad44911188e13456e37d528aa0c29dee19ace0 Mon Sep 17 00:00:00 2001 From: Kavindi487 Date: Sun, 22 Mar 2026 15:38:27 +0530 Subject: [PATCH 2/2] temporary demo fix for appointment/chat access --- .../PatientPortal/AppointmentsCard.jsx | 37 +-- .../bookings/AdminConfirmAppointments.jsx | 83 +++--- .../bookings/AppointmentsTable.jsx | 251 ++++++++++++++++++ 3 files changed, 293 insertions(+), 78 deletions(-) create mode 100644 src/components/PatientPortal/bookings/AppointmentsTable.jsx diff --git a/src/components/PatientPortal/AppointmentsCard.jsx b/src/components/PatientPortal/AppointmentsCard.jsx index 524c4f8..df5a2ad 100644 --- a/src/components/PatientPortal/AppointmentsCard.jsx +++ b/src/components/PatientPortal/AppointmentsCard.jsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import { Activity, Video, User } from "lucide-react"; import BookingModal from "../../components/PatientPortal/bookings/BookingPage"; +import AppointmentsTable from "../../components/PatientPortal/bookings/AppointmentsTable"; import { getUserAppointments, getDoctorAppointments, confirmAppointmentDoctor } from "../../api/api.js"; const AppointmentsCard = ({ isDoctorView = false, doctorId = null }) => { @@ -117,6 +118,15 @@ const AppointmentsCard = ({ isDoctorView = false, doctorId = null }) => { {/* Body */} {loading ? (

Loading appointments...

+ ) : isDoctorView ? ( +
+ +
) : appointments.length === 0 ? (

No appointments found.

) : ( @@ -163,33 +173,6 @@ const AppointmentsCard = ({ isDoctorView = false, doctorId = null }) => {
)} - - {/* Doctor Confirmation Input */} - {isDoctorView && - apt.appointmentStatus === "PENDING" && - apt.paymentStatus === "SUCCESS" && ( -
- - handleLinkChange(apt.appointmentId, e.target.value) - } - /> - -
- )}
); diff --git a/src/components/PatientPortal/bookings/AdminConfirmAppointments.jsx b/src/components/PatientPortal/bookings/AdminConfirmAppointments.jsx index 721209a..23f02da 100644 --- a/src/components/PatientPortal/bookings/AdminConfirmAppointments.jsx +++ b/src/components/PatientPortal/bookings/AdminConfirmAppointments.jsx @@ -1,68 +1,49 @@ import React, { useEffect, useState } from "react"; -import { getAllAppointments, confirmAppointment } from "../../../api/api"; +import { getAllAppointments } from "../../../api/api"; +import AppointmentsTable from "./AppointmentsTable"; export default function AdminConfirmAppointments() { const [appointments, setAppointments] = useState([]); - const [input, setInput] = useState({}); + const [loading, setLoading] = useState(true); useEffect(() => { load(); }, []); const load = async () => { - const data = await getAllAppointments(); - setAppointments(Array.isArray(data) ? data : []); - }; - - const confirm = async (a) => { - const params = - a.appointmentType === "Physical" - ? { physicalLocation: input[a.id] } - : { zoomLink: input[a.id] }; - - await confirmAppointment(a.id, params); - load(); + setLoading(true); + try { + const data = await getAllAppointments(); + setAppointments(Array.isArray(data) ? data : []); + } catch (e) { + console.error(e); + setAppointments([]); + } finally { + setLoading(false); + } }; return ( -
-

Admin Confirm Appointments

- - {appointments.map((a) => ( -
-

Doctor: {a.doctorName}

-

Specialty: {a.specialty}

-

Type: {a.appointmentType}

-

Date: {a.bookingDate}

-

Time: {a.bookingTime}

-

Status: {a.appointmentStatus}

-

Payment: {a.paymentStatus}

-

Location / Link: {a.locationOrLink}

- - {a.appointmentStatus === "PENDING" && - a.paymentStatus === "SUCCESS" && ( - <> - - setInput({ ...input, [a.id]: e.target.value }) - } - /> - - - )} +
+
+

Admin Confirm Appointments

+

+ Review all system bookings across all doctors and manually set locations or meeting links. +

+
+ + {loading ? ( +

Loading system appointments...

+ ) : ( +
+
- ))} + )}
); } diff --git a/src/components/PatientPortal/bookings/AppointmentsTable.jsx b/src/components/PatientPortal/bookings/AppointmentsTable.jsx new file mode 100644 index 0000000..28c4c2a --- /dev/null +++ b/src/components/PatientPortal/bookings/AppointmentsTable.jsx @@ -0,0 +1,251 @@ +import React, { useState } from "react"; +import { + Calendar, + Clock, + Video, + User, + Save, + CheckCircle, + ExternalLink, + Loader2, + UserCircle +} from "lucide-react"; +import { confirmAppointmentDoctor, confirmAppointment } from "../../../api/api.js"; + +export default function AppointmentsTable({ appointments, isAdminView, isDoctorView, refreshData }) { + const [links, setLinks] = useState({}); + const [loadingIds, setLoadingIds] = useState(new Set()); + + const handleLinkChange = (id, value) => { + setLinks((prev) => ({ ...prev, [id]: value })); + }; + + const handleConfirm = async (apt) => { + try { + // If it's already confirmed, they might just be updating the link + const linkValue = links[apt.appointmentId] || apt.locationOrLink; + if (!linkValue) return alert("Please enter a link or location"); + + // Set loading state for this specific row + setLoadingIds(prev => new Set(prev).add(apt.appointmentId)); + + const params = + apt.appointmentType === "Physical" + ? { physicalLocation: linkValue } + : { zoomLink: linkValue }; + + if (isAdminView) { + // use admin API + await confirmAppointment(apt.appointmentId, params); + } else { + // use doctor API + await confirmAppointmentDoctor(apt.appointmentId, params); + } + + refreshData(); + } catch (err) { + console.error("Confirm error:", err); + alert("Failed to confirm appointment"); + } finally { + // Remove loading state + setLoadingIds(prev => { + const next = new Set(prev); + next.delete(apt.appointmentId); + return next; + }); + } + }; + + if (!appointments || appointments.length === 0) { + return ( +
+
+ +
+

No appointments found

+

When bookings are made, they will appear here.

+
+ ); + } + + return ( +
+
+ + + + + + {isAdminView && } + + + + + + + + + + {appointments.map((apt, index) => { + const dateObj = new Date(apt.bookingDate); + const formattedDate = !Number.isNaN(dateObj.getTime()) + ? dateObj.toLocaleDateString("en-CA") // YYYY-MM-DD + : apt.bookingDate; + + const timeText = apt.bookingTime ? apt.bookingTime.slice(0, 5) : "—"; + const isOnline = String(apt.appointmentType || "").toLowerCase() === "online"; + const isPending = apt.appointmentStatus === "PENDING"; + + const currentLinkValue = links[apt.appointmentId] !== undefined + ? links[apt.appointmentId] + : (apt.locationOrLink || ""); + + const isLoading = loadingIds.has(apt.appointmentId); + + return ( + + {/* Index */} + + + {/* Patient Info */} + + + {/* Doctor Info (Admin View Only) */} + {isAdminView && ( + + )} + + {/* Date */} + + + {/* Time */} + + + {/* Type */} + + + {/* Status */} + + + {/* Link / Location Input */} + + + {/* Actions */} + + + ); + })} + +
#Patient DetailsDoctorDateTimeTypeStatusLink / LocationAction
{index + 1} +
+
+ +
+
+

+ {apt.patientName || "Unknown Patient"} +

+

+ {apt.reason || "No reason"} +

+
+
+
+

{apt.doctorName || "—"}

+

{apt.specialty}

+
+
+ + {formattedDate} +
+
+
+ + {timeText} +
+
+ + {isOnline ? + + + {isPending ? : } + {apt.appointmentStatus} + + + {apt.paymentStatus !== "SUCCESS" && isPending ? ( +

Waiting for Payment

+ ) : ( +
+ {!isPending && apt.locationOrLink && ( + + )} +
+ handleLinkChange(apt.appointmentId, e.target.value)} + className="flex-1 min-w-[180px] bg-gray-50 border border-gray-200 text-gray-700 text-xs rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all" + /> + +
+
+ )} +
+ {(isPending || apt.locationOrLink !== currentLinkValue) ? ( + + ) : ( + + + Confirmed + + )} +
+
+
+ ); +}