171 lines
5.7 KiB
TypeScript
171 lines
5.7 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import * as React from 'react';
|
||
|
|
import { CalendarCheck, Loader2, X } from 'lucide-react';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import {
|
||
|
|
Dialog,
|
||
|
|
DialogContent,
|
||
|
|
DialogDescription,
|
||
|
|
DialogFooter,
|
||
|
|
DialogHeader,
|
||
|
|
DialogTitle,
|
||
|
|
} from '@/components/ui/dialog';
|
||
|
|
import { DatePicker } from '@/components/DatePicker';
|
||
|
|
import { ClockTimePicker } from '@/components/ClockTimePicker';
|
||
|
|
import { DurationPicker } from '@/components/DurationPicker';
|
||
|
|
import type { Appointment } from '@/lib/models/appointments';
|
||
|
|
|
||
|
|
interface ScheduleAppointmentDialogProps {
|
||
|
|
open: boolean;
|
||
|
|
onOpenChange: (open: boolean) => void;
|
||
|
|
appointment: Appointment | null;
|
||
|
|
scheduledDate: Date | undefined;
|
||
|
|
setScheduledDate: (date: Date | undefined) => void;
|
||
|
|
scheduledTime: string;
|
||
|
|
setScheduledTime: (time: string) => void;
|
||
|
|
scheduledDuration: number;
|
||
|
|
setScheduledDuration: (duration: number) => void;
|
||
|
|
onSchedule: () => Promise<void>;
|
||
|
|
isScheduling: boolean;
|
||
|
|
isDark?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ScheduleAppointmentDialog({
|
||
|
|
open,
|
||
|
|
onOpenChange,
|
||
|
|
appointment,
|
||
|
|
scheduledDate,
|
||
|
|
setScheduledDate,
|
||
|
|
scheduledTime,
|
||
|
|
setScheduledTime,
|
||
|
|
scheduledDuration,
|
||
|
|
setScheduledDuration,
|
||
|
|
onSchedule,
|
||
|
|
isScheduling,
|
||
|
|
isDark = false,
|
||
|
|
}: ScheduleAppointmentDialogProps) {
|
||
|
|
const formatDate = (date: Date) => {
|
||
|
|
return date.toLocaleDateString("en-US", {
|
||
|
|
weekday: "long",
|
||
|
|
month: "long",
|
||
|
|
day: "numeric",
|
||
|
|
year: "numeric",
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
const formatTime = (timeString: string) => {
|
||
|
|
const [hours, minutes] = timeString.split(":").map(Number);
|
||
|
|
const date = new Date();
|
||
|
|
date.setHours(hours);
|
||
|
|
date.setMinutes(minutes);
|
||
|
|
return date.toLocaleTimeString("en-US", {
|
||
|
|
hour: "numeric",
|
||
|
|
minute: "2-digit",
|
||
|
|
hour12: true,
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
|
|
<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">
|
||
|
|
<DialogTitle className={`text-2xl font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
||
|
|
Schedule Appointment
|
||
|
|
</DialogTitle>
|
||
|
|
<DialogDescription className={`text-base ${isDark ? "text-gray-400" : "text-gray-500"}`}>
|
||
|
|
{appointment
|
||
|
|
? `Set date and time for ${appointment.first_name} ${appointment.last_name}'s appointment`
|
||
|
|
: "Set date and time for this appointment"}
|
||
|
|
</DialogDescription>
|
||
|
|
</DialogHeader>
|
||
|
|
|
||
|
|
<div className="space-y-6 py-4">
|
||
|
|
{/* Date Selection */}
|
||
|
|
<div className="space-y-3">
|
||
|
|
<label className={`text-sm font-semibold ${isDark ? "text-gray-300" : "text-gray-700"}`}>
|
||
|
|
Select Date *
|
||
|
|
</label>
|
||
|
|
<div className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
||
|
|
<DatePicker
|
||
|
|
date={scheduledDate}
|
||
|
|
setDate={setScheduledDate}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Time Selection */}
|
||
|
|
<div className="space-y-3 -mt-2">
|
||
|
|
<div className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
||
|
|
<ClockTimePicker
|
||
|
|
time={scheduledTime}
|
||
|
|
setTime={setScheduledTime}
|
||
|
|
label="Select Time *"
|
||
|
|
isDark={isDark}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Duration Selection */}
|
||
|
|
<div className="space-y-3">
|
||
|
|
<div className={`p-4 rounded-xl border ${isDark ? "bg-gray-700/50 border-gray-600" : "bg-gray-50 border-gray-200"}`}>
|
||
|
|
<DurationPicker
|
||
|
|
duration={scheduledDuration}
|
||
|
|
setDuration={setScheduledDuration}
|
||
|
|
label="Duration"
|
||
|
|
isDark={isDark}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Preview */}
|
||
|
|
{scheduledDate && scheduledTime && (
|
||
|
|
<div className={`p-4 rounded-xl border ${isDark ? "bg-blue-500/10 border-blue-500/30" : "bg-blue-50 border-blue-200"}`}>
|
||
|
|
<p className={`text-sm font-medium mb-2 ${isDark ? "text-blue-300" : "text-blue-700"}`}>
|
||
|
|
Appointment Preview
|
||
|
|
</p>
|
||
|
|
<div className="space-y-1">
|
||
|
|
<p className={`text-base font-semibold ${isDark ? "text-white" : "text-gray-900"}`}>
|
||
|
|
{formatDate(scheduledDate)}
|
||
|
|
</p>
|
||
|
|
<p className={`text-sm ${isDark ? "text-gray-300" : "text-gray-700"}`}>
|
||
|
|
{formatTime(scheduledTime)} • {scheduledDuration} minutes
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<DialogFooter className="gap-3 pt-4">
|
||
|
|
<Button
|
||
|
|
variant="outline"
|
||
|
|
onClick={() => onOpenChange(false)}
|
||
|
|
disabled={isScheduling}
|
||
|
|
className={`h-12 px-6 ${isDark ? "border-gray-700 text-gray-300 hover:bg-gray-700" : ""}`}
|
||
|
|
>
|
||
|
|
Cancel
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
onClick={onSchedule}
|
||
|
|
disabled={isScheduling || !scheduledDate || !scheduledTime}
|
||
|
|
className="h-12 px-6 bg-blue-600 hover:bg-blue-700 text-white"
|
||
|
|
>
|
||
|
|
{isScheduling ? (
|
||
|
|
<>
|
||
|
|
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
|
||
|
|
Scheduling...
|
||
|
|
</>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
<CalendarCheck className="w-5 h-5 mr-2" />
|
||
|
|
Schedule Appointment
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</Button>
|
||
|
|
</DialogFooter>
|
||
|
|
</DialogContent>
|
||
|
|
</Dialog>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|