import { useState } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { queryClient } from "@/client/rpc-client"; export function BookingForm() { const [selectedTreatment, setSelectedTreatment] = useState(""); const [customerName, setCustomerName] = useState(""); const [customerEmail, setCustomerEmail] = useState(""); const [customerPhone, setCustomerPhone] = useState(""); const [appointmentDate, setAppointmentDate] = useState(""); const [selectedSlotId, setSelectedSlotId] = useState(""); const [notes, setNotes] = useState(""); const [agbAccepted, setAgbAccepted] = useState(false); const [inspirationPhoto, setInspirationPhoto] = useState(""); const [photoPreview, setPhotoPreview] = useState(""); const [errorMessage, setErrorMessage] = useState(""); const { data: treatments } = useQuery( queryClient.treatments.live.list.experimental_liveOptions() ); // Lade alle Slots live und filtere freie Slots const { data: allSlots } = useQuery( queryClient.availability.live.list.experimental_liveOptions() ); // Filtere freie Slots und entferne vergangene Termine const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD const freeSlots = (allSlots || []).filter((s) => { // Nur freie Slots if (s.status !== "free") return false; // Nur zukünftige oder heutige Termine if (s.date < today) return false; // Für heute: nur zukünftige Uhrzeiten if (s.date === today) { const now = new Date(); const currentTime = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`; if (s.time <= currentTime) return false; } return true; }); const availableDates = Array.from(new Set(freeSlots.map((s) => s.date))).sort(); const slotsByDate = appointmentDate ? freeSlots.filter((s) => s.date === appointmentDate) : []; const { mutate: createBooking, isPending } = useMutation( queryClient.bookings.create.mutationOptions() ); const selectedTreatmentData = treatments?.find((t) => t.id === selectedTreatment); const availableSlots = slotsByDate || []; // Slots sind bereits gefiltert // Debug logging (commented out - uncomment if needed) // console.log("Debug - All slots:", allSlots); // console.log("Debug - Free slots:", freeSlots); // console.log("Debug - Available dates:", availableDates); // console.log("Debug - Selected date:", appointmentDate); // console.log("Debug - Slots by date:", slotsByDate); // console.log("Debug - Available slots:", availableSlots); // Additional debugging for slot status // if (allSlots && allSlots.length > 0) { // const statusCounts = allSlots.reduce((acc, slot) => { // acc[slot.status] = (acc[slot.status] || 0) + 1; // return acc; // }, {} as Record); // console.log("Debug - Slot status counts:", statusCounts); // } const handlePhotoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // Check file size (max 2MB for better performance) if (file.size > 2 * 1024 * 1024) { alert("Das Foto ist zu groß. Bitte wähle ein Bild unter 2MB."); return; } // Check file type if (!file.type.startsWith('image/')) { alert("Bitte wähle nur Bilddateien aus."); return; } // Compress the image before converting to base64 const compressImage = (file: File, maxWidth: number = 800, quality: number = 0.8): Promise => { return new Promise((resolve, reject) => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const img = new Image(); img.onload = () => { // Calculate new dimensions let { width, height } = img; if (width > maxWidth) { height = (height * maxWidth) / width; width = maxWidth; } canvas.width = width; canvas.height = height; // Draw and compress ctx?.drawImage(img, 0, 0, width, height); const compressedDataUrl = canvas.toDataURL('image/jpeg', quality); resolve(compressedDataUrl); }; img.onerror = reject; img.src = URL.createObjectURL(file); }); }; try { const compressedDataUrl = await compressImage(file); setInspirationPhoto(compressedDataUrl); setPhotoPreview(compressedDataUrl); // console.log(`Photo compressed: ${file.size} bytes → ${compressedDataUrl.length} chars`); } catch (error) { console.error('Photo compression failed:', error); alert('Fehler beim Verarbeiten des Bildes. Bitte versuche es mit einem anderen Bild.'); return; } }; const removePhoto = () => { setInspirationPhoto(""); setPhotoPreview(""); // Reset file input const fileInput = document.getElementById('photo-upload') as HTMLInputElement; if (fileInput) fileInput.value = ''; }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setErrorMessage(""); // Clear any previous error messages // console.log("Form submitted with data:", { // selectedTreatment, // customerName, // customerEmail, // customerPhone, // appointmentDate, // selectedSlotId, // agbAccepted // }); if (!selectedTreatment || !customerName || !customerEmail || !customerPhone || !appointmentDate || !selectedSlotId) { setErrorMessage("Bitte fülle alle erforderlichen Felder aus."); return; } if (!agbAccepted) { setErrorMessage("Bitte bestätige die Kenntnisnahme der Allgemeinen Geschäftsbedingungen."); return; } // Email validation now handled in backend before slot reservation const slot = availableSlots.find((s) => s.id === selectedSlotId); const appointmentTime = slot?.time || ""; // console.log("Creating booking with data:", { // treatmentId: selectedTreatment, // customerName, // customerEmail, // customerPhone, // appointmentDate, // appointmentTime, // notes, // inspirationPhoto, // slotId: selectedSlotId, // }); createBooking( { treatmentId: selectedTreatment, customerName, customerEmail, customerPhone, appointmentDate, appointmentTime, notes, inspirationPhoto, slotId: selectedSlotId, }, { onSuccess: () => { setSelectedTreatment(""); setCustomerName(""); setCustomerEmail(""); setCustomerPhone(""); setAppointmentDate(""); setSelectedSlotId(""); setNotes(""); setAgbAccepted(false); setInspirationPhoto(""); setPhotoPreview(""); setErrorMessage(""); // Reset file input const fileInput = document.getElementById('photo-upload') as HTMLInputElement; if (fileInput) fileInput.value = ''; alert("Buchung erfolgreich erstellt! Wir werden dich kontaktieren, um deinen Termin zu bestätigen."); }, onError: (error: any) => { console.error("Booking error:", error); // Simple error handling for oRPC errors let errorText = "Ein unbekannter Fehler ist aufgetreten."; if (error?.cause?.message) { errorText = error.cause.message; } else if (error?.message && error.message !== "Internal server error") { errorText = error.message; } setErrorMessage(errorText); }, } ); }; // Get minimum date (today) – nicht mehr genutzt, Datumsauswahl erfolgt aus freien Slots return (

Buche deine Nagelbehandlung

{/* Treatment Selection */}
{selectedTreatmentData && (

{selectedTreatmentData.description}

)}
{/* Customer Information */}
setCustomerName(e.target.value)} className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500" required />
setCustomerEmail(e.target.value)} className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500" required />
setCustomerPhone(e.target.value)} className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500" required />
{/* Date and Time Selection */}
{availableDates.length === 0 && (

Aktuell keine freien Termine verfügbar.

)}
{appointmentDate && availableSlots.length === 0 && (

Keine freien Zeitslots für {appointmentDate} verfügbar.

)}
{/* Notes */}