Implement appointment cancellation and rescheduling in admin view #68
@ -21,7 +21,7 @@ import {
|
|||||||
Pencil,
|
Pencil,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAppTheme } from "@/components/ThemeProvider";
|
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 { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -57,6 +57,8 @@ export default function AppointmentDetailPage() {
|
|||||||
const [isRescheduling, setIsRescheduling] = useState(false);
|
const [isRescheduling, setIsRescheduling] = useState(false);
|
||||||
const [isStartingMeeting, setIsStartingMeeting] = useState(false);
|
const [isStartingMeeting, setIsStartingMeeting] = useState(false);
|
||||||
const [isEndingMeeting, setIsEndingMeeting] = useState(false);
|
const [isEndingMeeting, setIsEndingMeeting] = useState(false);
|
||||||
|
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
||||||
|
const [isCancelling, setIsCancelling] = useState(false);
|
||||||
const { theme } = useAppTheme();
|
const { theme } = useAppTheme();
|
||||||
const isDark = theme === "dark";
|
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) => {
|
const copyToClipboard = (text: string, label: string) => {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
toast.success(`${label} copied to clipboard`);
|
toast.success(`${label} copied to clipboard`);
|
||||||
@ -384,10 +436,34 @@ export default function AppointmentDetailPage() {
|
|||||||
{appointment.scheduled_datetime && (
|
{appointment.scheduled_datetime && (
|
||||||
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
||||||
<div className={`px-6 py-4 border-b ${isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50"}`}>
|
<div className={`px-6 py-4 border-b ${isDark ? "border-gray-700 bg-gray-800/50" : "border-gray-200 bg-gray-50/50"}`}>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? "text-white" : "text-gray-900"}`}>
|
<h2 className={`text-lg font-semibold flex items-center gap-2 ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||||
<Calendar className={`w-5 h-5 ${isDark ? "text-rose-400" : "text-rose-600"}`} />
|
<Calendar className={`w-5 h-5 ${isDark ? "text-rose-400" : "text-rose-600"}`} />
|
||||||
Scheduled Appointment
|
Scheduled Appointment
|
||||||
</h2>
|
</h2>
|
||||||
|
{appointment.status === "scheduled" && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
// Initialize reschedule fields with current appointment values
|
||||||
|
if (appointment.scheduled_datetime) {
|
||||||
|
const scheduledDate = new Date(appointment.scheduled_datetime);
|
||||||
|
setRescheduleDate(scheduledDate);
|
||||||
|
const hours = scheduledDate.getHours().toString().padStart(2, "0");
|
||||||
|
const minutes = scheduledDate.getMinutes().toString().padStart(2, "0");
|
||||||
|
setRescheduleTime(`${hours}:${minutes}`);
|
||||||
|
}
|
||||||
|
if (appointment.scheduled_duration) {
|
||||||
|
setRescheduleDuration(appointment.scheduled_duration);
|
||||||
|
}
|
||||||
|
setRescheduleDialogOpen(true);
|
||||||
|
}}
|
||||||
|
className={`p-2 rounded-lg transition-colors ${isDark ? "hover:bg-gray-700 text-gray-300 hover:text-white" : "hover:bg-gray-100 text-gray-600 hover:text-gray-900"}`}
|
||||||
|
title="Reschedule Appointment"
|
||||||
|
>
|
||||||
|
<Pencil className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
@ -715,6 +791,22 @@ export default function AppointmentDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Cancel Appointment Button */}
|
||||||
|
{appointment.status === "scheduled" && (
|
||||||
|
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
||||||
|
<div className="p-6">
|
||||||
|
<Button
|
||||||
|
onClick={() => setCancelDialogOpen(true)}
|
||||||
|
variant="outline"
|
||||||
|
className={`w-full h-12 text-sm sm:text-base font-medium border-red-600 text-red-600 hover:bg-red-50 ${isDark ? "hover:bg-red-900/20 border-red-500" : ""}`}
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4 sm:w-5 sm:h-5 mr-2 shrink-0" />
|
||||||
|
<span className="text-center">Cancel Appointment</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Meeting Button (if scheduled) */}
|
{/* Meeting Button (if scheduled) */}
|
||||||
{appointment.status === "scheduled" && appointment.moderator_join_url && (
|
{appointment.status === "scheduled" && appointment.moderator_join_url && (
|
||||||
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gradient-to-br from-blue-900/20 to-purple-900/20 border-blue-800/30" : "bg-gradient-to-br from-blue-50 to-purple-50 border-blue-200"}`}>
|
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gradient-to-br from-blue-900/20 to-purple-900/20 border-blue-800/30" : "bg-gradient-to-br from-blue-50 to-purple-50 border-blue-200"}`}>
|
||||||
@ -729,10 +821,10 @@ export default function AppointmentDetailPage() {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
disabled
|
disabled
|
||||||
className={`flex items-center justify-center gap-2 w-full cursor-not-allowed h-12 rounded-lg text-base font-medium transition-colors ${isDark ? "bg-gray-700 text-gray-500" : "bg-gray-300 text-gray-500"}`}
|
className={`flex items-center justify-center gap-2 w-full cursor-not-allowed h-12 rounded-lg text-sm sm:text-base font-medium transition-colors ${isDark ? "bg-gray-700 text-gray-500" : "bg-gray-300 text-gray-500"}`}
|
||||||
>
|
>
|
||||||
<Video className="w-5 h-5" />
|
<Video className="w-4 h-4 sm:w-5 sm:h-5 shrink-0" />
|
||||||
Meeting has ended
|
<span className="text-center">Meeting has ended</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -771,26 +863,26 @@ export default function AppointmentDetailPage() {
|
|||||||
href={appointment.moderator_join_url}
|
href={appointment.moderator_join_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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`}
|
||||||
>
|
>
|
||||||
<Video className="w-5 h-5" />
|
<Video className="w-4 h-4 sm:w-5 sm:h-5 shrink-0" />
|
||||||
Join Now
|
<span className="text-center">Join Now</span>
|
||||||
</a>
|
</a>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleEndMeeting}
|
onClick={handleEndMeeting}
|
||||||
disabled={isEndingMeeting}
|
disabled={isEndingMeeting}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={`w-full h-12 text-base font-medium border-red-600 text-red-600 hover:bg-red-50 ${isDark ? "hover:bg-red-900/20" : ""}`}
|
className={`w-full h-12 text-sm sm:text-base font-medium border-red-600 text-red-600 hover:bg-red-50 ${isDark ? "hover:bg-red-900/20" : ""}`}
|
||||||
>
|
>
|
||||||
{isEndingMeeting ? (
|
{isEndingMeeting ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
|
<Loader2 className="w-4 h-4 sm:w-5 sm:h-5 mr-2 animate-spin shrink-0" />
|
||||||
Ending...
|
<span className="text-center">Ending...</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<X className="w-5 h-5 mr-2" />
|
<X className="w-4 h-4 sm:w-5 sm:h-5 mr-2 shrink-0" />
|
||||||
End Meeting
|
<span className="text-center">End Meeting</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@ -803,17 +895,17 @@ export default function AppointmentDetailPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleStartMeeting}
|
onClick={handleStartMeeting}
|
||||||
disabled={isStartingMeeting}
|
disabled={isStartingMeeting}
|
||||||
className="w-full bg-green-600 hover:bg-green-700 text-white h-12 text-base font-medium"
|
className="w-full bg-green-600 hover:bg-green-700 text-white h-12 text-sm sm:text-base font-medium"
|
||||||
>
|
>
|
||||||
{isStartingMeeting ? (
|
{isStartingMeeting ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
|
<Loader2 className="w-4 h-4 sm:w-5 sm:h-5 mr-2 animate-spin shrink-0" />
|
||||||
Starting...
|
<span className="text-center">Starting...</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Video className="w-5 h-5 mr-2" />
|
<Video className="w-4 h-4 sm:w-5 sm:h-5 mr-2 shrink-0" />
|
||||||
Start Meeting
|
<span className="text-center">Start Meeting</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@ -842,6 +934,24 @@ export default function AppointmentDetailPage() {
|
|||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Reschedule Appointment Dialog */}
|
||||||
|
<ScheduleAppointmentDialog
|
||||||
|
open={rescheduleDialogOpen}
|
||||||
|
onOpenChange={setRescheduleDialogOpen}
|
||||||
|
appointment={appointment}
|
||||||
|
scheduledDate={rescheduleDate}
|
||||||
|
setScheduledDate={setRescheduleDate}
|
||||||
|
scheduledTime={rescheduleTime}
|
||||||
|
setScheduledTime={setRescheduleTime}
|
||||||
|
scheduledDuration={rescheduleDuration}
|
||||||
|
setScheduledDuration={setRescheduleDuration}
|
||||||
|
onSchedule={handleReschedule}
|
||||||
|
isScheduling={isRescheduling}
|
||||||
|
isDark={isDark}
|
||||||
|
title="Reschedule Appointment"
|
||||||
|
description={appointment ? `Change the date and time for ${appointment.first_name} ${appointment.last_name}'s appointment` : "Change the date and time for this appointment"}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Reject Appointment Dialog */}
|
{/* Reject Appointment Dialog */}
|
||||||
<Dialog open={rejectDialogOpen} onOpenChange={setRejectDialogOpen}>
|
<Dialog open={rejectDialogOpen} onOpenChange={setRejectDialogOpen}>
|
||||||
<DialogContent className={`max-w-2xl ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
<DialogContent className={`max-w-2xl ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
||||||
@ -902,6 +1012,47 @@ export default function AppointmentDetailPage() {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Cancel Appointment Confirmation Dialog */}
|
||||||
|
<Dialog open={cancelDialogOpen} onOpenChange={setCancelDialogOpen}>
|
||||||
|
<DialogContent className={isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className={isDark ? "text-white" : "text-gray-900"}>
|
||||||
|
Cancel Appointment
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className={isDark ? "text-gray-400" : "text-gray-500"}>
|
||||||
|
Are you sure you want to cancel this appointment? This action cannot be undone.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setCancelDialogOpen(false)}
|
||||||
|
disabled={isCancelling}
|
||||||
|
className={isDark ? "border-gray-600 text-gray-300 hover:bg-gray-700" : ""}
|
||||||
|
>
|
||||||
|
No, Keep Appointment
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleCancelAppointment}
|
||||||
|
disabled={isCancelling}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white"
|
||||||
|
>
|
||||||
|
{isCancelling ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
Cancelling...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<X className="w-4 h-4 mr-2" />
|
||||||
|
Yes, Cancel Appointment
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,19 +15,10 @@ import {
|
|||||||
MessageSquare,
|
MessageSquare,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Copy,
|
Copy,
|
||||||
X,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAppTheme } from "@/components/ThemeProvider";
|
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 { Button } from "@/components/ui/button";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Navbar } from "@/components/Navbar";
|
import { Navbar } from "@/components/Navbar";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import type { Appointment } from "@/lib/models/appointments";
|
import type { Appointment } from "@/lib/models/appointments";
|
||||||
@ -39,8 +30,6 @@ export default function UserAppointmentDetailPage() {
|
|||||||
|
|
||||||
const [appointment, setAppointment] = useState<Appointment | null>(null);
|
const [appointment, setAppointment] = useState<Appointment | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
|
|
||||||
const [isCancelling, setIsCancelling] = useState(false);
|
|
||||||
const { theme } = useAppTheme();
|
const { theme } = useAppTheme();
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
|
|
||||||
@ -149,26 +138,6 @@ export default function UserAppointmentDetailPage() {
|
|||||||
toast.success(`${label} copied to clipboard`);
|
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) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen ${isDark ? "bg-gray-900" : "bg-gray-50"}`}>
|
<div className={`min-h-screen ${isDark ? "bg-gray-900" : "bg-gray-50"}`}>
|
||||||
@ -642,65 +611,9 @@ export default function UserAppointmentDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Cancel Appointment Button */}
|
|
||||||
{appointment.status === "scheduled" && (
|
|
||||||
<div className={`rounded-2xl border shadow-sm overflow-hidden ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
|
||||||
<div className="p-6">
|
|
||||||
<Button
|
|
||||||
onClick={() => setCancelDialogOpen(true)}
|
|
||||||
variant="outline"
|
|
||||||
className={`w-full h-12 text-base font-medium border-red-600 text-red-600 hover:bg-red-50 ${isDark ? "hover:bg-red-900/20 border-red-500" : ""}`}
|
|
||||||
>
|
|
||||||
<X className="w-5 h-5 mr-2" />
|
|
||||||
Cancel Appointment
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Cancel Appointment Confirmation Dialog */}
|
|
||||||
<Dialog open={cancelDialogOpen} onOpenChange={setCancelDialogOpen}>
|
|
||||||
<DialogContent className={isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className={isDark ? "text-white" : "text-gray-900"}>
|
|
||||||
Cancel Appointment
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription className={isDark ? "text-gray-400" : "text-gray-500"}>
|
|
||||||
Are you sure you want to cancel this appointment? This action cannot be undone.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setCancelDialogOpen(false)}
|
|
||||||
disabled={isCancelling}
|
|
||||||
className={isDark ? "border-gray-600 text-gray-300 hover:bg-gray-700" : ""}
|
|
||||||
>
|
|
||||||
No, Keep Appointment
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleCancelAppointment}
|
|
||||||
disabled={isCancelling}
|
|
||||||
className="bg-red-600 hover:bg-red-700 text-white"
|
|
||||||
>
|
|
||||||
{isCancelling ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
||||||
Cancelling...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<X className="w-4 h-4 mr-2" />
|
|
||||||
Yes, Cancel Appointment
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,8 @@ interface ScheduleAppointmentDialogProps {
|
|||||||
onSchedule: () => Promise<void>;
|
onSchedule: () => Promise<void>;
|
||||||
isScheduling: boolean;
|
isScheduling: boolean;
|
||||||
isDark?: boolean;
|
isDark?: boolean;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ScheduleAppointmentDialog({
|
export function ScheduleAppointmentDialog({
|
||||||
@ -44,6 +46,8 @@ export function ScheduleAppointmentDialog({
|
|||||||
onSchedule,
|
onSchedule,
|
||||||
isScheduling,
|
isScheduling,
|
||||||
isDark = false,
|
isDark = false,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
}: ScheduleAppointmentDialogProps) {
|
}: ScheduleAppointmentDialogProps) {
|
||||||
const formatDate = (date: Date) => {
|
const formatDate = (date: Date) => {
|
||||||
return date.toLocaleDateString("en-US", {
|
return date.toLocaleDateString("en-US", {
|
||||||
@ -71,12 +75,12 @@ export function ScheduleAppointmentDialog({
|
|||||||
<DialogContent className={`max-w-4xl max-h-[90vh] overflow-y-auto ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
<DialogContent className={`max-w-4xl max-h-[90vh] overflow-y-auto ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"}`}>
|
||||||
<DialogHeader className="pb-4">
|
<DialogHeader className="pb-4">
|
||||||
<DialogTitle className={`text-2xl font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
<DialogTitle className={`text-2xl font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
||||||
Schedule Appointment
|
{title || "Schedule Appointment"}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className={`text-base ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
<DialogDescription className={`text-base ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||||||
{appointment
|
{description || (appointment
|
||||||
? `Set date and time for ${appointment.first_name} ${appointment.last_name}'s 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")}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user