timeslots API
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
'use client'
|
||||
'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 { bookingApi } from '@/lib/pocketbase';
|
||||
import { BookingType, TimeSlot } from '@/types/bookings';
|
||||
import BookingTypeSelector from './BookingTypeSelector';
|
||||
import DateSelector from './DateSelector';
|
||||
import TimeSlotSelector from './TimeSlotSelector';
|
||||
@@ -15,10 +19,17 @@ interface FormData {
|
||||
}
|
||||
|
||||
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 [availableSlotsByDate, setAvailableSlotsByDate] = useState<{ [date: string]: { start_time: string; end_time: 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',
|
||||
@@ -29,25 +40,30 @@ const BookingInterface = () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Mock data - will be replaced with API calls
|
||||
const bookingTypes = [
|
||||
{ id: '1', name: 'wheel_rental', display_name: 'Wheel Rental', requires_payment: false, price_per_person: 0 },
|
||||
{ id: '2', name: 'hand_building', display_name: 'Hand Building Coworking', requires_payment: false, price_per_person: 0 },
|
||||
{ id: '3', name: 'personal_workshop', display_name: 'Personal Workshop', requires_payment: true, price_per_person: 75 },
|
||||
{ id: '4', name: 'group_workshop', display_name: 'Group Workshop', requires_payment: true, price_per_person: 50 }
|
||||
];
|
||||
|
||||
// Mock available time slots
|
||||
const timeSlots = [
|
||||
{ id: '1', start_time: '09:00', end_time: '11:00', available: true },
|
||||
{ id: '2', start_time: '11:30', end_time: '13:30', available: true },
|
||||
{ id: '3', start_time: '14:00', end_time: '16:00', available: false },
|
||||
{ id: '4', start_time: '16:30', end_time: '18:30', available: true },
|
||||
{ id: '5', start_time: '19:00', end_time: '21:00', available: true }
|
||||
];
|
||||
|
||||
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');
|
||||
@@ -61,14 +77,13 @@ const BookingInterface = () => {
|
||||
customerName: data.customerName,
|
||||
customerEmail: data.customerEmail,
|
||||
startTime: selectedDate && selectedTimeSlotData
|
||||
? `${selectedDate}T${selectedTimeSlotData.start_time}:00`
|
||||
? `${selectedDate}T${selectedTimeSlotData.start_time}`
|
||||
: '',
|
||||
endTime: selectedDate && selectedTimeSlotData
|
||||
? `${selectedDate}T${selectedTimeSlotData.end_time}:00`
|
||||
? `${selectedDate}T${selectedTimeSlotData.end_time}`
|
||||
: '',
|
||||
participantsCount: data.participantsCount,
|
||||
};
|
||||
|
||||
console.log(formData);
|
||||
};
|
||||
|
||||
@@ -76,15 +91,58 @@ const BookingInterface = () => {
|
||||
setParticipantsCount(count);
|
||||
};
|
||||
|
||||
const handleBookingTypeChange = (typeId: string) => {
|
||||
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_capacity);
|
||||
}
|
||||
|
||||
// Fetch time slots for the selected booking type
|
||||
try {
|
||||
const timeSlots = await bookingApi.getTimeSlotsForBookingType(typeId);
|
||||
|
||||
// Generate available time slots grouped by date
|
||||
const generatedSlotsByDate = bookingApi.generateAvailableTimeSlots(timeSlots);
|
||||
const availableDatesFromAPI = Object.keys(generatedSlotsByDate).sort();
|
||||
|
||||
// Update available dates and slots data for the date selector
|
||||
setAvailableDates(availableDatesFromAPI);
|
||||
setAvailableSlotsByDate(generatedSlotsByDate);
|
||||
} catch (error) {
|
||||
console.error('Error fetching time slots:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDateChange = (date: string) => {
|
||||
setSelectedDate(date);
|
||||
setSelectedTimeSlot('');
|
||||
|
||||
// Get time slots for the selected date from availableSlotsByDate
|
||||
const slotsForDate = availableSlotsByDate[date] || [];
|
||||
console.log(`Time slots for ${date}:`, slotsForDate);
|
||||
|
||||
// Convert to TimeSlot format for TimeSlotSelector
|
||||
const formattedTimeSlots: TimeSlot[] = slotsForDate.map((slot, index) => ({
|
||||
id: `slot-${date}-${index}`,
|
||||
start_time: slot.start_time,
|
||||
end_time: slot.end_time,
|
||||
is_active: true,
|
||||
max_capacity: 8,
|
||||
booking_types: [selectedBookingType],
|
||||
is_reccuring: false,
|
||||
recurrence_pattern: undefined,
|
||||
resources: [],
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString()
|
||||
}));
|
||||
|
||||
setTimeSlots(formattedTimeSlots);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -92,22 +150,49 @@ const BookingInterface = () => {
|
||||
<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">Book Your Session</h1>
|
||||
<p className="text-gray-600">Choose your pottery experience</p>
|
||||
<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 */}
|
||||
<BookingTypeSelector
|
||||
bookingTypes={bookingTypes}
|
||||
selectedBookingType={selectedBookingType}
|
||||
onBookingTypeChange={handleBookingTypeChange}
|
||||
/>
|
||||
{!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}
|
||||
/>
|
||||
)}
|
||||
@@ -122,13 +207,14 @@ const BookingInterface = () => {
|
||||
)}
|
||||
|
||||
{/* Customer Details */}
|
||||
{selectedTimeSlot && (
|
||||
{selectedTimeSlot && selectedBookingTypeData && (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<CustomerDetails
|
||||
register={register}
|
||||
errors={errors}
|
||||
participantsCount={participantsCount}
|
||||
onParticipantsCountChange={handleParticipantsCountChange}
|
||||
bookingType={selectedBookingTypeData}
|
||||
/>
|
||||
|
||||
{/* Summary & Submit */}
|
||||
|
||||
Reference in New Issue
Block a user