Files
vitrify-me/apps/web/components/BookingForm.tsx
2025-08-27 02:50:13 +04:00

251 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import React, { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslations } from 'next-intl';
import { BookingService } from '@/lib/bookingService';
import { BookingType, TimeSlot } from '@/types/bookings';
import BookingTypeSelector from './BookingTypeSelector';
import DateSelector from './DateSelector';
import TimeSlotSelector from './TimeSlotSelector';
import CustomerDetails from './CustomerDetails';
import BookingSummary from './BookingSummary';
interface FormData {
customerName: string;
customerEmail: string;
participantsCount: number;
}
const BookingInterface = () => {
const t = useTranslations('bookingForm');
const [selectedDate, setSelectedDate] = useState('');
const [selectedTimeSlot, setSelectedTimeSlot] = useState('');
const [selectedBookingType, setSelectedBookingType] = useState('');
const [participantsCount, setParticipantsCount] = useState(1);
const [bookingTypes, setBookingTypes] = useState<BookingType[]>([]);
const [timeSlots, setTimeSlots] = useState<TimeSlot[]>([]);
const [availableDates, setAvailableDates] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { register, handleSubmit, formState: { errors, isValid }, setValue, trigger } = useForm<FormData>({
mode: 'onChange',
defaultValues: {
customerName: '',
customerEmail: '',
participantsCount: 1
}
});
const selectedBookingTypeData = bookingTypes.find(bt => bt.id === selectedBookingType);
// Fetch booking types on mount
useEffect(() => {
const fetchBookingTypes = async () => {
try {
setLoading(true);
setError(null);
const types = await BookingService.getBookingTypes();
setBookingTypes(types);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load booking types');
} finally {
setLoading(false);
}
};
fetchBookingTypes();
}, []);
// Available dates are now handled in handleBookingTypeChange
// Time slots are now handled in handleDateChange
useEffect(() => {
setValue('participantsCount', participantsCount);
trigger('participantsCount');
}, [participantsCount, setValue, trigger]);
const onSubmit = (data: FormData) => {
const selectedTimeSlotData = timeSlots.find(slot => slot.id === selectedTimeSlot);
const formData = {
bookingTypeId: selectedBookingType,
customerName: data.customerName,
customerEmail: data.customerEmail,
startTime: selectedDate && selectedTimeSlotData
? `${selectedDate}T${selectedTimeSlotData.start_time}`
: '',
endTime: selectedDate && selectedTimeSlotData
? `${selectedDate}T${selectedTimeSlotData.end_time}`
: '',
participantsCount: data.participantsCount,
};
console.log(formData);
};
const handleParticipantsCountChange = (count: number) => {
setParticipantsCount(count);
};
const handleBookingTypeChange = async (typeId: string) => {
setSelectedBookingType(typeId);
setSelectedDate('');
setSelectedTimeSlot('');
setTimeSlots([]);
// Set participants count to booking type's minimum capacity
const bookingType = bookingTypes.find(bt => bt.id === typeId);
if (bookingType) {
setParticipantsCount(bookingType.min_participants_capacity);
}
// Fetch available dates from server API (with capacity pre-calculated)
try {
const response = await fetch(`/api/booking-types/${typeId}/available-dates`);
const data = await response.json();
if (response.ok) {
setAvailableDates(data.availableDates);
} else {
console.error('Error fetching available dates:', data.error);
setAvailableDates([]);
}
} catch (error) {
console.error('Error fetching available dates:', error);
setAvailableDates([]);
}
};
const handleDateChange = async (date: string) => {
setSelectedDate(date);
setSelectedTimeSlot('');
// Fetch time slots with capacity from server API
try {
const response = await fetch(`/api/booking-types/${selectedBookingType}/time-slots?date=${date}`);
const data = await response.json();
if (response.ok) {
// Convert server response to TimeSlot format
const formattedTimeSlots: TimeSlot[] = data.timeSlots.map((slot: any) => ({
id: slot.id,
start_time: slot.start_time,
end_time: slot.end_time,
is_active: slot.is_active,
booking_capacity: slot.maxCapacity,
booking_types: [selectedBookingType],
is_reccuring: false,
recurrence_pattern: undefined,
resources: [],
created: new Date().toISOString(),
updated: new Date().toISOString(),
availableCapacity: slot.availableCapacity // Add capacity info for UI
}));
setTimeSlots(formattedTimeSlots);
} else {
console.error('Error fetching time slots:', data.error);
setTimeSlots([]);
}
} catch (error) {
console.error('Error fetching time slots:', error);
setTimeSlots([]);
}
};
return (
<div className="min-h-screen bg-gray-50 p-4">
<div className="max-w-2xl mx-auto">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('title')}</h1>
<p className="text-gray-600">{t('subtitle')}</p>
</div>
<div className="space-y-8">
{/* Loading State */}
{loading && (
<div className="bg-white rounded-xl p-6 shadow-sm text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p className="text-gray-600 mt-2">{t('loading')}</p>
</div>
)}
{/* Error State */}
{error && (
<div className="bg-white rounded-xl p-6 shadow-sm text-center">
<div className="text-red-500 mb-2"></div>
<p className="text-gray-800 font-medium mb-2">{t('noBookings')}</p>
<p className="text-gray-600 text-sm">{error}</p>
</div>
)}
{/* Booking Type Selection */}
{!loading && !error && bookingTypes.length === 0 && (
<div className="bg-white rounded-xl p-6 shadow-sm text-center">
<p className="text-gray-800">{t('noBookings')}</p>
<p className="text-gray-600 text-sm mt-2">{t('checkBackLater')}</p>
</div>
)}
{!loading && !error && bookingTypes.length > 0 && (
<BookingTypeSelector
bookingTypes={bookingTypes}
selectedBookingType={selectedBookingType}
onBookingTypeChange={handleBookingTypeChange}
/>
)}
{/* Date Selection */}
{selectedBookingType && (
<DateSelector
selectedDate={selectedDate}
availableDates={availableDates}
onDateChange={handleDateChange}
/>
)}
{/* Time Slot Selection */}
{selectedDate && (
<TimeSlotSelector
timeSlots={timeSlots}
selectedTimeSlot={selectedTimeSlot}
onTimeSlotChange={setSelectedTimeSlot}
/>
)}
{/* Customer Details */}
{selectedTimeSlot && selectedBookingTypeData && (
<form onSubmit={handleSubmit(onSubmit)}>
<CustomerDetails
register={register}
errors={errors}
participantsCount={participantsCount}
onParticipantsCountChange={handleParticipantsCountChange}
bookingType={selectedBookingTypeData}
/>
{/* Summary & Submit */}
{isValid && (
<div className="mt-8">
<BookingSummary
selectedBookingTypeData={selectedBookingTypeData}
selectedDate={selectedDate}
selectedTimeSlot={selectedTimeSlot}
timeSlots={timeSlots}
participantsCount={participantsCount}
onSubmit={handleSubmit(onSubmit)}
/>
</div>
)}
</form>
)}
</div>
</div>
</div>
);
};
export default BookingInterface;