195 lines
7.2 KiB
TypeScript
195 lines
7.2 KiB
TypeScript
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<string>("");
|
|
const [notes, setNotes] = useState("");
|
|
|
|
const { data: treatments } = useQuery(
|
|
queryClient.treatments.live.list.experimental_liveOptions()
|
|
);
|
|
const { data: slotsByDate } = useQuery(
|
|
appointmentDate
|
|
? queryClient.availability.live.byDate.experimental_liveOptions(appointmentDate)
|
|
: queryClient.availability.live.byDate.experimental_liveOptions("")
|
|
);
|
|
|
|
const { mutate: createBooking, isPending } = useMutation(
|
|
queryClient.bookings.create.mutationOptions()
|
|
);
|
|
|
|
const selectedTreatmentData = treatments?.find(t => t.id === selectedTreatment);
|
|
const availableSlots = (slotsByDate || []).filter(s => s.status === "free");
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!selectedTreatment || !customerName || !customerEmail || !customerPhone || !appointmentDate || !selectedSlotId) {
|
|
alert("Bitte fülle alle erforderlichen Felder aus");
|
|
return;
|
|
}
|
|
const slot = availableSlots.find(s => s.id === selectedSlotId);
|
|
const appointmentTime = slot?.time || "";
|
|
createBooking({
|
|
treatmentId: selectedTreatment,
|
|
customerName,
|
|
customerEmail,
|
|
customerPhone,
|
|
appointmentDate,
|
|
appointmentTime,
|
|
notes,
|
|
slotId: selectedSlotId,
|
|
}, {
|
|
onSuccess: () => {
|
|
setSelectedTreatment("");
|
|
setCustomerName("");
|
|
setCustomerEmail("");
|
|
setCustomerPhone("");
|
|
setAppointmentDate("");
|
|
setSelectedSlotId("");
|
|
setNotes("");
|
|
alert("Buchung erfolgreich erstellt! Wir werden dich kontaktieren, um deinen Termin zu bestätigen.");
|
|
}
|
|
});
|
|
};
|
|
|
|
// Get minimum date (today)
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
return (
|
|
<div className="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6">
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-6">Buche deine Nagelbehandlung</h2>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
{/* Treatment Selection */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Behandlung auswählen *
|
|
</label>
|
|
<select
|
|
value={selectedTreatment}
|
|
onChange={(e) => setSelectedTreatment(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
|
|
>
|
|
<option value="">Wähle eine Behandlung</option>
|
|
{treatments?.map((treatment) => (
|
|
<option key={treatment.id} value={treatment.id}>
|
|
{treatment.name} - ${(treatment.price / 100).toFixed(2)} ({treatment.duration} min)
|
|
</option>
|
|
))}
|
|
</select>
|
|
{selectedTreatmentData && (
|
|
<p className="mt-2 text-sm text-gray-600">{selectedTreatmentData.description}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Customer Information */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Vollständiger Name *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={customerName}
|
|
onChange={(e) => 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
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
E-Mail *
|
|
</label>
|
|
<input
|
|
type="email"
|
|
value={customerEmail}
|
|
onChange={(e) => 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
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Telefonnummer *
|
|
</label>
|
|
<input
|
|
type="tel"
|
|
value={customerPhone}
|
|
onChange={(e) => 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
|
|
/>
|
|
</div>
|
|
|
|
{/* Date and Time Selection */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Gewünschtes Datum *
|
|
</label>
|
|
<input
|
|
type="date"
|
|
value={appointmentDate}
|
|
onChange={(e) => setAppointmentDate(e.target.value)}
|
|
min={today}
|
|
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Verfügbare Uhrzeit *
|
|
</label>
|
|
<select
|
|
value={selectedSlotId}
|
|
onChange={(e) => setSelectedSlotId(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"
|
|
disabled={!appointmentDate || !selectedTreatment}
|
|
required
|
|
>
|
|
<option value="">Zeit auswählen</option>
|
|
{availableSlots
|
|
.sort((a, b) => a.time.localeCompare(b.time))
|
|
.map((slot) => (
|
|
<option key={slot.id} value={slot.id}>
|
|
{slot.time} ({slot.durationMinutes} min)
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Notes */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Zusätzliche Notizen
|
|
</label>
|
|
<textarea
|
|
value={notes}
|
|
onChange={(e) => setNotes(e.target.value)}
|
|
rows={3}
|
|
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500"
|
|
placeholder="Besondere Wünsche oder Informationen..."
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={isPending}
|
|
className="w-full bg-pink-600 text-white py-3 px-4 rounded-md hover:bg-pink-700 focus:ring-2 focus:ring-pink-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
|
|
>
|
|
{isPending ? "Wird gebucht..." : "Termin buchen"}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
);
|
|
} |