"use client"; import { useState, useEffect } from "react"; import { useParams, useRouter } from "next/navigation"; import { Calendar, Clock, User, Video, CalendarCheck, X, Loader2, ArrowLeft, Mail, Phone as PhoneIcon, MessageSquare, CheckCircle2, ExternalLink, Copy, MapPin, } from "lucide-react"; import { useAppTheme } from "@/components/ThemeProvider"; import { getAppointmentDetail, scheduleAppointment, rejectAppointment } from "@/lib/actions/appointments"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { ScheduleAppointmentDialog } from "@/components/ScheduleAppointmentDialog"; import { toast } from "sonner"; import type { Appointment } from "@/lib/models/appointments"; export default function AppointmentDetailPage() { const params = useParams(); const router = useRouter(); const appointmentId = params.id as string; const [appointment, setAppointment] = useState(null); const [loading, setLoading] = useState(true); const [scheduleDialogOpen, setScheduleDialogOpen] = useState(false); const [rejectDialogOpen, setRejectDialogOpen] = useState(false); const [scheduledDate, setScheduledDate] = useState(undefined); const [scheduledTime, setScheduledTime] = useState("09:00"); const [scheduledDuration, setScheduledDuration] = useState(60); const [rejectionReason, setRejectionReason] = useState(""); const [isScheduling, setIsScheduling] = useState(false); const [isRejecting, setIsRejecting] = useState(false); const { theme } = useAppTheme(); const isDark = theme === "dark"; useEffect(() => { const fetchAppointment = async () => { if (!appointmentId) return; setLoading(true); try { const data = await getAppointmentDetail(appointmentId); setAppointment(data); } catch (error) { toast.error("Failed to load appointment details"); router.push("/admin/booking"); } finally { setLoading(false); } }; fetchAppointment(); }, [appointmentId, router]); const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric", }); }; const formatTime = (dateString: string) => { const date = new Date(dateString); return date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true, }); }; const formatShortDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); }; const getStatusColor = (status: string) => { const normalized = status.toLowerCase(); if (isDark) { switch (normalized) { case "scheduled": return "bg-blue-500/20 text-blue-300 border-blue-500/30"; case "completed": return "bg-green-500/20 text-green-300 border-green-500/30"; case "rejected": case "cancelled": return "bg-red-500/20 text-red-300 border-red-500/30"; case "pending_review": case "pending": return "bg-yellow-500/20 text-yellow-300 border-yellow-500/30"; default: return "bg-gray-700 text-gray-200 border-gray-600"; } } switch (normalized) { case "scheduled": return "bg-blue-50 text-blue-700 border-blue-200"; case "completed": return "bg-green-50 text-green-700 border-green-200"; case "rejected": case "cancelled": return "bg-red-50 text-red-700 border-red-200"; case "pending_review": case "pending": return "bg-yellow-50 text-yellow-700 border-yellow-200"; default: return "bg-gray-100 text-gray-700 border-gray-300"; } }; const formatStatus = (status: string) => { return status.replace("_", " ").replace(/\b\w/g, (l) => l.toUpperCase()); }; const handleSchedule = async () => { if (!appointment || !scheduledDate) return; setIsScheduling(true); try { const dateTime = new Date(scheduledDate); const [hours, minutes] = scheduledTime.split(":").map(Number); dateTime.setHours(hours, minutes, 0, 0); await scheduleAppointment(appointment.id, { scheduled_datetime: dateTime.toISOString(), scheduled_duration: scheduledDuration, }); toast.success("Appointment scheduled successfully"); setScheduleDialogOpen(false); // Refresh appointment data const updated = await getAppointmentDetail(appointment.id); setAppointment(updated); } catch (error: any) { toast.error(error.message || "Failed to schedule appointment"); } finally { setIsScheduling(false); } }; const handleReject = async () => { if (!appointment) return; setIsRejecting(true); try { await rejectAppointment(appointment.id, { rejection_reason: rejectionReason || undefined, }); toast.success("Appointment rejected successfully"); setRejectDialogOpen(false); // Refresh appointment data const updated = await getAppointmentDetail(appointment.id); setAppointment(updated); } catch (error: any) { toast.error(error.message || "Failed to reject appointment"); } finally { setIsRejecting(false); } }; const copyToClipboard = (text: string, label: string) => { navigator.clipboard.writeText(text); toast.success(`${label} copied to clipboard`); }; if (loading) { return (

Loading appointment details...

); } if (!appointment) { return (

Appointment not found

); } return (
{/* Page Header */}
{appointment.first_name[0]}{appointment.last_name[0]}

{appointment.first_name} {appointment.last_name}

Appointment Request

{appointment.status === "scheduled" && } {formatStatus(appointment.status)}
{/* Main Content - Left Column (2/3) */}
{/* Patient Information Card */}

Patient Information

Full Name

{appointment.first_name} {appointment.last_name}

Email Address

{appointment.email}

{appointment.phone && (

Phone Number

{appointment.phone}

)}
{/* Appointment Details Card */} {appointment.scheduled_datetime && (

Scheduled Appointment

{formatDate(appointment.scheduled_datetime)}

{formatTime(appointment.scheduled_datetime)}

{appointment.scheduled_duration && (

{appointment.scheduled_duration} minutes

)}
)} {/* Selected Slots */} {appointment.selected_slots && Array.isArray(appointment.selected_slots) && appointment.selected_slots.length > 0 && (

Selected Time Slots

{appointment.selected_slots.map((slot: any, idx: number) => { const dayNames: Record = { 0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday", 4: "Friday", 5: "Saturday", 6: "Sunday", }; const timeSlotLabels: Record = { morning: "Morning", afternoon: "Lunchtime", evening: "Evening", }; const dayName = dayNames[slot.day] || `Day ${slot.day}`; const timeSlot = String(slot.time_slot).toLowerCase().trim(); const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot; return (

{dayName}

{timeLabel}

); })}
)} {/* Matching Slots */} {(() => { // Check if matching_availability is a MatchingAvailability object with matching_slots const matchingAvailability = appointment.matching_availability as any; const hasMatchingSlots = matchingAvailability && matchingAvailability.matching_slots && Array.isArray(matchingAvailability.matching_slots) && matchingAvailability.matching_slots.length > 0; const isArrayFormat = Array.isArray(matchingAvailability) && matchingAvailability.length > 0; if (!hasMatchingSlots && !isArrayFormat) return null; const dayNames: Record = { 0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday", 4: "Friday", 5: "Saturday", 6: "Sunday", }; const timeSlotLabels: Record = { morning: "Morning", afternoon: "Lunchtime", evening: "Evening", }; // Get matching slots from MatchingAvailability object const matchingSlots = hasMatchingSlots ? matchingAvailability.matching_slots : null; const totalMatchingSlots = hasMatchingSlots ? matchingAvailability.total_matching_slots : null; const preferencesMatch = hasMatchingSlots ? matchingAvailability.preferences_match_availability : appointment.are_preferences_available; return (

Matching Slots {preferencesMatch !== undefined && ( {preferencesMatch ? "All Available" : "Partially Available"} )}

{hasMatchingSlots && totalMatchingSlots && (

Found {totalMatchingSlots} matching time slot{totalMatchingSlots !== 1 ? 's' : ''} that match your selected preferences:

)} {!hasMatchingSlots && (

These are the available time slots that match your selected preferences:

)} {hasMatchingSlots ? ( // Display matching_slots from MatchingAvailability object
{matchingSlots.map((slot: any, idx: number) => { const dayName = dayNames[slot.day] || `Day ${slot.day}`; const timeSlot = String(slot.time_slot).toLowerCase().trim(); const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot; return (

{dayName}

{timeLabel}

{formatShortDate(slot.date)}

); })}
) : ( // Display array format (legacy)
{(matchingAvailability as any[]).map((match: any, idx: number) => (

{match.day_name || "Unknown Day"}

{formatShortDate(match.date || match.date_obj || "")}

{match.available_slots && Array.isArray(match.available_slots) && match.available_slots.length > 0 && (
{match.available_slots.map((slot: string, slotIdx: number) => { const normalizedSlot = String(slot).toLowerCase().trim(); return ( {timeSlotLabels[normalizedSlot] || slot} ); })}
)}
))}
)}
); })()} {/* Reason */} {appointment.reason && (

Reason for Appointment

{appointment.reason}

)} {/* Rejection Reason */} {appointment.rejection_reason && (

Rejection Reason

{appointment.rejection_reason}

)} {/* Meeting Information */} {appointment.jitsi_meet_url && (

{appointment.jitsi_room_id && (

Meeting Room ID

{appointment.jitsi_room_id}

)}

Meeting Link

{appointment.can_join_meeting ? ( <> {appointment.jitsi_meet_url} ) : ( <>
{appointment.jitsi_meet_url}
)}
{appointment.can_join_meeting !== undefined && (

{appointment.can_join_meeting ? "Meeting is active - You can join now" : "Meeting is not available yet"}

)}
)}
{/* Sidebar - Right Column (1/3) */}
{/* Quick Info Card */}

Quick Info

Created

{formatShortDate(appointment.created_at)}

{formatTime(appointment.created_at)}

Status

{appointment.status === "scheduled" && } {formatStatus(appointment.status)}
{/* Action Buttons */} {appointment.status === "pending_review" && (
)} {/* Join Meeting Button (if scheduled) */} {appointment.status === "scheduled" && appointment.jitsi_meet_url && (
{appointment.can_join_meeting ? ( ) : ( )}
)}
{/* Schedule Appointment Dialog */} {/* Reject Appointment Dialog */} Reject Appointment Request Reject appointment request from {appointment.first_name} {appointment.last_name}