"use client"; import type React from "react"; import { useState, useMemo, useEffect } from "react"; import { toast } from "sonner"; import { Card, CardContent, CardHeader, CardTitle, CardDescription, } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Calendar, Clock, User, Mail, Phone, CheckCircle, Stethoscope, AlertCircle, FileText, ArrowLeft, ArrowRight, Loader2, CreditCard, Shield, Check, MapPin, Heart, } from "lucide-react"; import { Field, FieldLabel, FieldContent } from "@/components/ui/field"; import { InputGroup, InputGroupAddon, InputGroupInput, } from "@/components/ui/input-group"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { cn } from "@/lib/utils"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; interface BookingFormProps { services?: Array<{ id: string; name: string; price: string | number; duration: string | number; category: string; description?: string; }>; dentists?: Array<{ id: string; name: string; specialization?: string; image?: string; }>; patientId?: string; } const MEDICAL_CONDITIONS = [ "Diabetes", "Heart Disease", "High Blood Pressure", "Bleeding Disorder", "Asthma", "Allergies (Drug/Food)", "Thyroid Disease", "Kidney Disease", "Liver Disease", "Cancer", "Osteoporosis", "Arthritis", ]; const MEDICATIONS_COMMON = [ "Aspirin", "Ibuprofen", "Warfarin (Blood Thinner)", "Antibiotics", "Blood Pressure Medication", "Diabetes Medication", "Thyroid Medication", "Antidepressants", ]; const DENTAL_HISTORY = [ "Regular Checkups", "Root Canal", "Extractions", "Crowns/Bridges", "Implants", "Orthodontics (Braces)", "Gum Disease Treatment", "Teeth Whitening", ]; const STEP_CONFIG = [ { id: 0, title: "Services", shortTitle: "Services", description: "Choose your dental service", icon: Stethoscope, }, { id: 1, title: "Date & Time", shortTitle: "Schedule", description: "Select appointment date and time", icon: Calendar, }, { id: 2, title: "Patient Info", shortTitle: "Info", description: "Your contact information", icon: User, }, { id: 3, title: "Medical History", shortTitle: "Medical", description: "Health information (optional)", icon: Heart, }, { id: 4, title: "Review & Confirm", shortTitle: "Review", description: "Confirm your appointment", icon: Check, }, ]; type ServiceSelection = { id: string; name: string; qty: number }; export default function BookingForm({ services: propServices = [], dentists: propDentists = [], patientId, }: BookingFormProps) { const [currentStep, setCurrentStep] = useState(0); const [isSubmitting, setIsSubmitting] = useState(false); const [isNewPatient, setIsNewPatient] = useState(true); const [showMedicalHistory, setShowMedicalHistory] = useState(false); // Auto-save to localStorage - memoize to prevent unnecessary re-renders const STORAGE_KEY = useMemo(() => `booking-form-${patientId}`, [patientId]); const services = useMemo( () => propServices && propServices.length > 0 ? propServices.map((service) => ({ ...service, price: service.price, duration: typeof service.duration === "string" ? Number.parseInt(service.duration) : service.duration, })) : [], [propServices] ); const dentists = propDentists && propDentists.length > 0 ? propDentists : []; const [formData, setFormData] = useState<{ services: ServiceSelection[]; preferredDate: string; preferredTime: string; dentistId: string; firstName: string; lastName: string; dateOfBirth: string; gender: string; email: string; contactNumber: string; preferredContact: string; address: string; city: string; postalCode: string; medicalConditions: string[]; otherConditions: string; currentMedications: string[]; otherMedications: string; allergies: string; lastDentalVisit: string; dentalHistory: string[]; hasMedicalUpdates: string; specialRequests: string; insuranceProvider: string; insurancePolicyNumber: string; emergencyContactName: string; emergencyContactPhone: string; smsReminders: boolean; consentToTreatment: boolean; }>({ services: [], preferredDate: "", preferredTime: "", dentistId: "", firstName: "", lastName: "", dateOfBirth: "", gender: "", email: "", contactNumber: "", preferredContact: "email", address: "", city: "", postalCode: "", medicalConditions: [], otherConditions: "", currentMedications: [], otherMedications: "", allergies: "", lastDentalVisit: "", dentalHistory: [], hasMedicalUpdates: "no", specialRequests: "", insuranceProvider: "", insurancePolicyNumber: "", emergencyContactName: "", emergencyContactPhone: "", smsReminders: true, consentToTreatment: false, }); const handleInputChange = (field: string, value: string | boolean) => { setFormData((prev) => ({ ...prev, [field]: value, })); }; const handleArrayToggle = (field: string, value: string) => { setFormData((prev) => { const array = prev[field as keyof typeof prev] as string[]; if (array.includes(value)) { return { ...prev, [field]: array.filter((item) => item !== value), }; } else { return { ...prev, [field]: [...array, value], }; } }); }; const handleServiceChange = (serviceId: string, qty: number) => { setFormData((prev) => { const existing = prev.services.find((s) => s.id === serviceId); let newServices; if (existing) { if (qty === 0) { newServices = prev.services.filter((s) => s.id !== serviceId); } else { newServices = prev.services.map((s) => s.id === serviceId ? { ...s, qty } : s ); } } else { const service = services.find((s) => s.id === serviceId); if (!service) return prev; newServices = [ ...prev.services, { id: serviceId, name: service.name, qty }, ]; } return { ...prev, services: newServices }; }); }; const parsePrice = (price: string | number): number => { if (!price) return 0; if (typeof price === "number") return price; if (price.toLowerCase().includes("contact")) return 0; const match = price.match(/₱?([\d,]+)/); if (match) { return Number.parseFloat(match[1].replace(/,/g, "")); } return 0; }; const { itemizedServices, subtotal, tax, totalDue } = useMemo(() => { const itemizedServices = formData.services .filter((s) => s.qty > 0) .map((s) => { const service = services.find((svc) => svc.id === s.id); const price = service ? parsePrice(service.price) : 0; const total = price * s.qty; return { description: service ? service.name : "", qty: s.qty, unitPrice: price, total, }; }); const subtotal = itemizedServices.reduce( (sum: number, s) => sum + s.total, 0 ); const tax = Math.round(subtotal * 0.12 * 100) / 100; const totalDue = subtotal + tax; return { itemizedServices, subtotal, tax, totalDue }; }, [formData.services, services]); const selectedDentist = dentists.find((d) => d.id === formData.dentistId); const validateStep = (step: number): boolean => { switch (step) { case 0: return formData.services.filter((s) => s.qty > 0).length > 0; case 1: return !!( formData.preferredDate && formData.preferredTime && formData.dentistId ); case 2: return !!( formData.firstName && formData.lastName && formData.email && formData.contactNumber && formData.dateOfBirth && formData.gender ); case 3: return true; case 4: return formData.consentToTreatment; default: return false; } }; const canProceed = validateStep(currentStep); const handleNext = () => { if (canProceed && currentStep < STEP_CONFIG.length - 1) { if ( currentStep === 2 && !showMedicalHistory && formData.hasMedicalUpdates === "no" ) { setCurrentStep(4); } else { setCurrentStep(currentStep + 1); } } }; const handleBack = () => { if (currentStep > 0) { if ( currentStep === 4 && !showMedicalHistory && formData.hasMedicalUpdates === "no" ) { setCurrentStep(2); } else { setCurrentStep(currentStep - 1); } } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.consentToTreatment) { toast.error("Please agree to the consent to treatment"); return; } const selectedServices = formData.services.filter((s) => s.qty > 0); if (selectedServices.length === 0) { toast.error("Please select at least one service"); return; } if (totalDue <= 0) { toast.error("Invalid total amount"); return; } setIsSubmitting(true); const appointmentData = { patientId, personalInfo: { firstName: formData.firstName, lastName: formData.lastName, dateOfBirth: formData.dateOfBirth, gender: formData.gender, email: formData.email, contactNumber: formData.contactNumber, preferredContact: formData.preferredContact, address: formData.address, city: formData.city, postalCode: formData.postalCode, }, medicalHistory: showMedicalHistory || formData.hasMedicalUpdates === "yes" ? { conditions: formData.medicalConditions, otherConditions: formData.otherConditions, medications: formData.currentMedications, otherMedications: formData.otherMedications, allergies: formData.allergies, lastDentalVisit: formData.lastDentalVisit, dentalHistory: formData.dentalHistory, } : null, appointment: { date: formData.preferredDate, time: formData.preferredTime, dentistId: formData.dentistId, dentistName: selectedDentist?.name, }, services: itemizedServices, insurance: { provider: formData.insuranceProvider, policyNumber: formData.insurancePolicyNumber, }, emergency: { contactName: formData.emergencyContactName, contactPhone: formData.emergencyContactPhone, }, preferences: { smsReminders: formData.smsReminders, }, specialRequests: formData.specialRequests, totals: { subtotal, tax, totalDue, }, }; try { const response = await fetch("/api/appointments/book", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ appointmentData, }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Failed to book appointment"); } localStorage.removeItem(STORAGE_KEY); toast.success( data.message || "Appointment booked successfully! Check your email for confirmation.", { description: "You will be redirected to your appointments page.", duration: 3000, } ); setTimeout(() => { window.location.href = "/patient/appointments"; }, 2000); } catch (error) { console.error("Error booking appointment:", error); toast.error("Failed to book appointment", { description: error instanceof Error ? error.message : "Unknown error. Please try again.", }); setIsSubmitting(false); } }; useEffect(() => { const saved = localStorage.getItem(STORAGE_KEY); if (saved) { try { const parsed = JSON.parse(saved); setFormData((prev) => ({ ...prev, ...parsed })); } catch (e) { console.error("Failed to load saved form data", e); } } }, [STORAGE_KEY]); useEffect(() => { const timer = setTimeout(() => { localStorage.setItem(STORAGE_KEY, JSON.stringify(formData)); }, 1000); return () => clearTimeout(timer); }, [formData, STORAGE_KEY]); const progressPercent = ((currentStep + 1) / STEP_CONFIG.length) * 100; return (
Book Your Appointment Complete in just a few minutes
{/* Step Indicators - Desktop */}
{STEP_CONFIG.map((step, idx) => { const isActive = idx === currentStep; const isCompleted = idx < currentStep; const Icon = step.icon; return (
{isCompleted ? ( ) : ( )}
{step.title}
); })}
{/* Step Indicators - Mobile */}
Step {currentStep + 1} of {STEP_CONFIG.length}
{/* Step 0: Service Selection */} {currentStep === 0 && (

Select Your Service

Choose the dental service you need

{services.length > 0 ? ( {Array.from( new Set(services.map((s) => s.category)) ).map((category) => ( {category} ))} {Array.from(new Set(services.map((s) => s.category))).map( (category) => (
{services .filter( (service) => service.category === category ) .map((service) => { const isSelected = formData.services.some( (s) => s.id === service.id && s.qty > 0 ); return (
handleServiceChange( service.id, isSelected ? 0 : 1 ) } className={cn( "relative p-5 border-2 cursor-pointer transition-all duration-200", isSelected ? "border-primary bg-accent shadow-lg ring-2 ring-primary/20" : "border-border hover:border-primary/60 hover:bg-accent/30" )} style={{ borderRadius: "var(--radius-md)", }} > {isSelected && (
)}

{service.name}

{service.description && (

{service.description}

)}
{service.duration} min
{typeof service.price === "number" ? `₱${service.price.toLocaleString()}` : service.price}
); })}
) )} ) : ( No services available. Please contact the clinic. )} {formData.services.filter((s) => s.qty > 0).length > 0 && (

Selected Services

{formData.services .filter((s) => s.qty > 0) .map((s) => { const service = services.find( (svc) => svc.id === s.id ); return (
{service?.name} ₱{service?.price.toLocaleString()}
); })}
)}
)} {/* Step 1: Date & Time Selection */} {currentStep === 1 && (

Choose Date & Time

Select your preferred appointment schedule

Preferred Dentist{" "} * handleInputChange("dentistId", value) } className="space-y-2" > {dentists.map((dentist) => ( ))}
Preferred Date{" "} * handleInputChange("preferredDate", e.target.value) } min={new Date().toISOString().split("T")[0]} required /> Preferred Time{" "} * handleInputChange("preferredTime", e.target.value) } required />
We'll confirm your appointment time based on availability
)} {/* Step 2: Personal Information */} {currentStep === 2 && (

Your Information

Help us serve you better

Are you a new patient? { setIsNewPatient(value === "yes"); setShowMedicalHistory(value === "yes"); if (value === "no") { handleInputChange("hasMedicalUpdates", "no"); } }} className="flex gap-4" > {!isNewPatient && ( Any updates to your medical history? { handleInputChange("hasMedicalUpdates", value); setShowMedicalHistory(value === "yes"); }} className="flex gap-4" > )}
First Name * handleInputChange("firstName", e.target.value) } placeholder="Dental" required autoComplete="given-name" /> Last Name * handleInputChange("lastName", e.target.value) } placeholder="Care" required autoComplete="family-name" /> Date of Birth{" "} * handleInputChange("dateOfBirth", e.target.value) } required autoComplete="bday" /> Gender * Email Address{" "} * handleInputChange("email", e.target.value) } placeholder="@example.com" required autoComplete="email" /> Phone Number * handleInputChange("contactNumber", e.target.value) } placeholder="+63 912 345 6789" required autoComplete="tel" />
Preferred Contact Method handleInputChange("preferredContact", value) } className="flex flex-wrap gap-4" >

Address (Optional)

Street Address handleInputChange("address", e.target.value) } placeholder="Baltan Street" autoComplete="street-address" />
City handleInputChange("city", e.target.value) } placeholder="Palawan" autoComplete="address-level2" /> Postal Code handleInputChange( "postalCode", e.target.value ) } placeholder="5300" autoComplete="postal-code" />
)} {/* Step 3: Medical History */} {currentStep === 3 && (

Medical History

Help us provide safe and effective treatment

Privacy Protected: All medical information is confidential and HIPAA compliant Medical Conditions
{MEDICAL_CONDITIONS.map((condition) => ( ))}
Other Medical Conditions