diff --git a/app/(admin)/admin/booking/[id]/page.tsx b/app/(admin)/admin/booking/[id]/page.tsx index 883faaf..e8bb34e 100644 --- a/app/(admin)/admin/booking/[id]/page.tsx +++ b/app/(admin)/admin/booking/[id]/page.tsx @@ -21,7 +21,7 @@ import { Pencil, } from "lucide-react"; import { useAppTheme } from "@/components/ThemeProvider"; -import { getAppointmentDetail, scheduleAppointment, rejectAppointment, listAppointments, startMeeting, endMeeting, rescheduleAppointment } from "@/lib/actions/appointments"; +import { getAppointmentDetail, scheduleAppointment, rejectAppointment, listAppointments, startMeeting, endMeeting, rescheduleAppointment, cancelAppointment } from "@/lib/actions/appointments"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -57,6 +57,8 @@ export default function AppointmentDetailPage() { const [isRescheduling, setIsRescheduling] = useState(false); const [isStartingMeeting, setIsStartingMeeting] = useState(false); const [isEndingMeeting, setIsEndingMeeting] = useState(false); + const [cancelDialogOpen, setCancelDialogOpen] = useState(false); + const [isCancelling, setIsCancelling] = useState(false); const { theme } = useAppTheme(); const isDark = theme === "dark"; @@ -210,6 +212,56 @@ export default function AppointmentDetailPage() { } }; + const handleReschedule = async () => { + if (!appointment || !rescheduleDate) return; + + setIsRescheduling(true); + try { + const dateTime = new Date(rescheduleDate); + const [hours, minutes] = rescheduleTime.split(":").map(Number); + dateTime.setHours(hours, minutes, 0, 0); + + const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + await rescheduleAppointment(appointment.id, { + new_scheduled_datetime: dateTime.toISOString(), + new_scheduled_duration: rescheduleDuration, + timezone: userTimezone, + }); + + toast.success("Appointment rescheduled successfully"); + setRescheduleDialogOpen(false); + + // Refresh appointment data + const updated = await getAppointmentDetail(appointment.id); + setAppointment(updated); + } catch (error: any) { + toast.error(error.message || "Failed to reschedule appointment"); + } finally { + setIsRescheduling(false); + } + }; + + 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(appointment.id); + setAppointment(updatedAppointment); + router.push("/admin/booking"); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Failed to cancel appointment"; + toast.error(errorMessage); + } finally { + setIsCancelling(false); + } + }; + const copyToClipboard = (text: string, label: string) => { navigator.clipboard.writeText(text); toast.success(`${label} copied to clipboard`); @@ -384,10 +436,34 @@ export default function AppointmentDetailPage() { {appointment.scheduled_datetime && (
-

- - Scheduled Appointment -

+
+

+ + Scheduled Appointment +

+ {appointment.status === "scheduled" && ( + + )} +
@@ -715,6 +791,22 @@ export default function AppointmentDetailPage() {
)} + {/* Cancel Appointment Button */} + {appointment.status === "scheduled" && ( +
+
+ +
+
+ )} + {/* Meeting Button (if scheduled) */} {appointment.status === "scheduled" && appointment.moderator_join_url && (
@@ -729,10 +821,10 @@ export default function AppointmentDetailPage() { return ( ); } @@ -771,26 +863,26 @@ export default function AppointmentDetailPage() { href={appointment.moderator_join_url} target="_blank" rel="noopener noreferrer" - className={`flex items-center justify-center gap-2 w-full bg-blue-600 hover:bg-blue-700 text-white h-12 rounded-lg text-base font-medium transition-colors`} + className={`flex items-center justify-center gap-2 w-full bg-blue-600 hover:bg-blue-700 text-white h-12 rounded-lg text-sm sm:text-base font-medium transition-colors`} > -
); } diff --git a/app/(user)/user/appointments/[id]/page.tsx b/app/(user)/user/appointments/[id]/page.tsx index 11a716c..73e06fe 100644 --- a/app/(user)/user/appointments/[id]/page.tsx +++ b/app/(user)/user/appointments/[id]/page.tsx @@ -15,19 +15,10 @@ import { MessageSquare, CheckCircle2, Copy, - X, } from "lucide-react"; import { useAppTheme } from "@/components/ThemeProvider"; -import { getAppointmentDetail, listAppointments, cancelAppointment } from "@/lib/actions/appointments"; +import { getAppointmentDetail, listAppointments } 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"; @@ -39,8 +30,6 @@ export default function UserAppointmentDetailPage() { 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"; @@ -149,26 +138,6 @@ export default function UserAppointmentDetailPage() { 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 (
@@ -642,65 +611,9 @@ export default function UserAppointmentDetailPage() {
)} - {/* 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. - - - - - - - - ); } diff --git a/components/ScheduleAppointmentDialog.tsx b/components/ScheduleAppointmentDialog.tsx index 201e229..71d10eb 100644 --- a/components/ScheduleAppointmentDialog.tsx +++ b/components/ScheduleAppointmentDialog.tsx @@ -29,6 +29,8 @@ interface ScheduleAppointmentDialogProps { onSchedule: () => Promise; isScheduling: boolean; isDark?: boolean; + title?: string; + description?: string; } export function ScheduleAppointmentDialog({ @@ -44,6 +46,8 @@ export function ScheduleAppointmentDialog({ onSchedule, isScheduling, isDark = false, + title, + description, }: ScheduleAppointmentDialogProps) { const formatDate = (date: Date) => { return date.toLocaleDateString("en-US", { @@ -71,12 +75,12 @@ export function ScheduleAppointmentDialog({ - Schedule Appointment + {title || "Schedule Appointment"} - {appointment + {description || (appointment ? `Set date and time for ${appointment.first_name} ${appointment.last_name}'s appointment` - : "Set date and time for this appointment"} + : "Set date and time for this appointment")}