"use client"; import { useState, useEffect } from "react"; import { useParams, useRouter } from "next/navigation"; import { Calendar, Clock, User, Video, CalendarCheck, Loader2, ArrowLeft, Mail, Phone as PhoneIcon, MessageSquare, CheckCircle2, Copy, X, } from "lucide-react"; import { useAppTheme } from "@/components/ThemeProvider"; import { getAppointmentDetail, listAppointments, cancelAppointment } from "@/lib/actions/appointments"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Navbar } from "@/components/Navbar"; import { toast } from "sonner"; import type { Appointment } from "@/lib/models/appointments"; export default function UserAppointmentDetailPage() { const params = useParams(); const router = useRouter(); const appointmentId = params.id as string; const [appointment, setAppointment] = useState(null); const [loading, setLoading] = useState(true); const [cancelDialogOpen, setCancelDialogOpen] = useState(false); const [isCancelling, setIsCancelling] = useState(false); const { theme } = useAppTheme(); const isDark = theme === "dark"; useEffect(() => { const fetchAppointment = async () => { if (!appointmentId) return; setLoading(true); try { // Fetch both detail and list to get selected_slots from list endpoint const [detailData, listData] = await Promise.all([ getAppointmentDetail(appointmentId), listAppointments().catch(() => []) // Fallback to empty array if list fails ]); // Find matching appointment in list to get selected_slots const listAppointment = Array.isArray(listData) ? listData.find((apt: Appointment) => apt.id === appointmentId) : null; // Merge selected_slots from list into detail data if (listAppointment && listAppointment.selected_slots && Array.isArray(listAppointment.selected_slots) && listAppointment.selected_slots.length > 0) { detailData.selected_slots = listAppointment.selected_slots; } setAppointment(detailData); } catch (error) { toast.error("Failed to load appointment details"); router.push("/user/dashboard"); } 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 copyToClipboard = (text: string, label: string) => { navigator.clipboard.writeText(text); toast.success(`${label} copied to clipboard`); }; const handleCancelAppointment = async () => { if (!appointment) return; setIsCancelling(true); try { await cancelAppointment(appointment.id); toast.success("Appointment cancelled successfully"); setCancelDialogOpen(false); // Refetch appointment to get updated status const updatedAppointment = await getAppointmentDetail(appointmentId); setAppointment(updatedAppointment); router.push("/user/dashboard"); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Failed to cancel appointment"; toast.error(errorMessage); } finally { setIsCancelling(false); } }; if (loading) { return (

Loading appointment details...

); } if (!appointment) { return (

Appointment not found

); } return (
{/* Page Header */}

Appointment Details

Request #{appointment.id.slice(0, 8)}

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

Appointment Information

Full Name

{appointment.first_name} {appointment.last_name}

Email Address

{appointment.email}

{appointment.phone && (

Phone Number

{appointment.phone}

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

Scheduled Appointment

{formatDate(appointment.scheduled_datetime)}

{formatTime(appointment.scheduled_datetime)}

{appointment.scheduled_duration && (

{appointment.meeting_duration_display || `${appointment.scheduled_duration} minutes`}

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

Selected Time Slots {appointment.are_preferences_available !== undefined && ( {appointment.are_preferences_available ? "All Available" : "Partially Available"} )}

{(() => { 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", }; // Time slot order: morning, afternoon (lunchtime), evening const timeSlotOrder: Record = { morning: 0, afternoon: 1, evening: 2, }; // Group slots by date const slotsByDate: Record = {}; appointment.selected_slots.forEach((slot: any) => { const date = slot.date || ""; if (!slotsByDate[date]) { slotsByDate[date] = []; } slotsByDate[date].push(slot); }); // Sort dates and slots within each date const sortedDates = Object.keys(slotsByDate).sort((a, b) => { return new Date(a).getTime() - new Date(b).getTime(); }); return (
{sortedDates.map((date) => { // Sort slots within this date by time slot order const slots = slotsByDate[date].sort((a: any, b: any) => { const aSlot = String(a.time_slot).toLowerCase().trim(); const bSlot = String(b.time_slot).toLowerCase().trim(); const aOrder = timeSlotOrder[aSlot] ?? 999; const bOrder = timeSlotOrder[bSlot] ?? 999; return aOrder - bOrder; }); return (

{formatShortDate(date)}

{slots.length > 0 && slots[0]?.day !== undefined && (

{dayNames[slots[0].day] || `Day ${slots[0].day}`}

)}
{slots.map((slot: any, idx: number) => { const timeSlot = String(slot.time_slot).toLowerCase().trim(); const timeLabel = timeSlotLabels[timeSlot] || slot.time_slot; return ( {timeLabel} ); })}
); })}
); })()}
)} {/* Reason */} {appointment.reason && (

Reason for Appointment

{appointment.reason}

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

Rejection Reason

{appointment.rejection_reason}

)} {/* Meeting Information */} {/* Video Meeting card hidden - users can join via the Join Now button in sidebar */} {/* {appointment.participant_join_url && (

{appointment.can_join_as_participant !== undefined && (

{appointment.can_join_as_participant ? "Meeting is active - You can join now" : "Meeting would be available shortly"}

)}
)} */}
{/* 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)}
{appointment.scheduled_datetime && (

Meeting Information

Meeting Start Time:

{formatDate(appointment.scheduled_datetime)} at {formatTime(appointment.scheduled_datetime)}

How to Access:

{`Up to 10 minutes before the meeting is scheduled to begin, click the "Join Now" button below when the meeting becomes available. The meeting will be accessible starting at ${formatTime(appointment.scheduled_datetime)}.`}

)}
{/* Join Meeting Button */} {appointment.status === "scheduled" && appointment.participant_join_url && (
{(() => { // Check if meeting has ended const endedAt = appointment.meeting_ended_at; const hasEnded = endedAt != null && endedAt !== ""; // If meeting has ended, show "Meeting has ended" if (hasEnded) { return ( ); } // Check if can join as participant (handle both boolean and string values) const canJoinAsParticipant = appointment.can_join_as_participant === true || appointment.can_join_as_participant === "true"; // Check if meeting has started (handle both field names) const startedAt = appointment.started_at || appointment.meeting_started_at; const hasStarted = startedAt != null && startedAt !== ""; // If can_join_as_participant != true, display "Meeting would be available shortly" if (!canJoinAsParticipant) { return ( ); } // If can_join_as_participant == true && started_at != null, show "Join Now" button if (hasStarted) { return ( ); } // If can_join_as_participant == true && started_at == null, show "Meeting would be available shortly" return ( ); })()}
)} {/* Cancel Appointment Button */} {appointment.status === "scheduled" && (
)}
{/* Cancel Appointment Confirmation Dialog */} Cancel Appointment Are you sure you want to cancel this appointment? This action cannot be undone.
); }