timeslots API

This commit is contained in:
Asya Vee
2025-08-26 23:36:24 +04:00
parent 47baf8dfe2
commit c0647fe512
47 changed files with 830 additions and 776 deletions

View File

@@ -1,5 +1,5 @@
import PocketBase from 'pocketbase';
import { BookingType, Resource, TimeSlot, Booking } from '@/types/booking';
import { BookingType, TimeSlot } from '@/types/bookings';
// Initialize PocketBase client
export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL || 'http://127.0.0.1:8090');
@@ -8,7 +8,6 @@ export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL || 'http
pb.autoCancellation(false);
// API functions for booking system
export const bookingApi = {
// Get all active booking types
async getBookingTypes(): Promise<BookingType[]> {
@@ -24,121 +23,103 @@ export const bookingApi = {
}
},
// Get available resources
async getResources(): Promise<Resource[]> {
// Get time slots for a specific booking type
async getTimeSlotsForBookingType(bookingTypeId: string): Promise<TimeSlot[]> {
try {
const records = await pb.collection('resources').getFullList<Resource>({
filter: 'is_active = true',
sort: 'type',
});
return records;
} catch (error) {
console.error('Error fetching resources:', error);
throw error;
}
},
const today = new Date();
const oneMonthFromToday = new Date();
oneMonthFromToday.setMonth(today.getMonth() + 1);
// Get available time slots for a specific booking type and date range
async getAvailableTimeSlots(
bookingTypeId: string,
startDate: Date,
endDate: Date
): Promise<TimeSlot[]> {
try {
const startDateStr = startDate.toISOString().split('T')[0];
const endDateStr = endDate.toISOString().split('T')[0];
const todayStr = today.toISOString().split('T')[0];
const oneMonthStr = oneMonthFromToday.toISOString().split('T')[0];
const records = await pb.collection('timeSlots').getFullList<TimeSlot>({
filter: `is_active = true && booking_types ~ "${bookingTypeId}" && start_time >= "${startDateStr}" && start_time <= "${endDateStr}"`,
filter: `is_active = true && booking_types ~ "${bookingTypeId}" && start_time >= "${todayStr}" && end_time <= "${oneMonthStr}"`,
sort: 'start_time',
});
return records;
} catch (error) {
console.error('Error fetching time slots:', error);
console.error('Error fetching time slots for booking type:', error);
throw error;
}
},
// Check if a time slot has availability
async checkTimeSlotAvailability(
timeSlotId: string,
startTime: string,
endTime: string,
participantsCount: number
): Promise<{ available: boolean; currentBookings: number }> {
try {
// Get existing confirmed bookings for this time slot
const existingBookings = await pb.collection('bookings').getFullList<Booking>({
filter: `status = "confirmed" && start_time >= "${startTime}" && end_time <= "${endTime}"`,
});
// Generate available time slots grouped by date
generateAvailableTimeSlots(timeSlots: TimeSlot[]): { [date: string]: { start_time: string; end_time: string }[] } {
const today = new Date();
const oneMonthFromToday = new Date();
oneMonthFromToday.setMonth(today.getMonth() + 1);
// Get the time slot to check max capacity
const timeSlot = await pb.collection('timeSlots').getOne<TimeSlot>(timeSlotId);
// Step 1: Generate massive array of individual time slots with specific dates
const allTimeSlots: { start_time: string; end_time: string }[] = [];
const currentBookings = existingBookings.reduce((sum, booking) =>
sum + (booking.participants_count || 0), 0
);
timeSlots.forEach(slot => {
if (!slot.is_reccuring || !slot.recurrence_pattern) {
// Handle non-recurring slots - use as is
allTimeSlots.push({
start_time: slot.start_time,
end_time: slot.end_time
});
return;
}
const availableSpots = timeSlot.max_capacity - currentBookings;
const available = availableSpots >= participantsCount;
// Handle recurring slots - generate dates based on recurrence pattern
const pattern = slot.recurrence_pattern;
const endDate = pattern.end_date ? new Date(pattern.end_date) : oneMonthFromToday;
const finalEndDate = endDate < oneMonthFromToday ? endDate : oneMonthFromToday;
return {
available,
currentBookings,
};
} catch (error) {
console.error('Error checking availability:', error);
throw error;
}
},
// Extract time from original start_time and end_time
const originalStartTime = new Date(slot.start_time);
const originalEndTime = new Date(slot.end_time);
const startHours = originalStartTime.getHours();
const startMinutes = originalStartTime.getMinutes();
const endHours = originalEndTime.getHours();
const endMinutes = originalEndTime.getMinutes();
// Create a new booking
async createBooking(bookingData: {
booking_type: string;
customer_name: string;
customer_email: string;
start_time: string;
end_time: string;
participants_count: number;
internal_notes?: string;
}): Promise<Booking> {
try {
const record = await pb.collection('bookings').create<Booking>({
...bookingData,
status: 'confirmed',
payment_status: 'not_required', // We'll handle payment logic later
payment_required: false,
});
return record;
} catch (error) {
console.error('Error creating booking:', error);
throw error;
}
},
let currentDate = new Date(today);
while (currentDate <= finalEndDate) {
const dayOfWeek = currentDate.getDay();
// Convert Sunday (0) to 6, Monday (1) to 0, etc. to match pattern format
const patternDay = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
if (pattern.days && pattern.days.includes(patternDay)) {
// Create specific datetime for this occurrence
const specificStartTime = new Date(currentDate);
specificStartTime.setHours(startHours, startMinutes, 0, 0);
const specificEndTime = new Date(currentDate);
specificEndTime.setHours(endHours, endMinutes, 0, 0);
allTimeSlots.push({
start_time: specificStartTime.toISOString(),
end_time: specificEndTime.toISOString()
});
}
currentDate.setDate(currentDate.getDate() + 1);
}
});
// Get booking by cancellation token (for cancellation flow)
async getBookingByToken(token: string): Promise<Booking | null> {
try {
const records = await pb.collection('bookings').getFullList<Booking>({
filter: `cancellation_token = "${token}"`,
});
return records[0] || null;
} catch (error) {
console.error('Error fetching booking by token:', error);
return null;
}
},
// Step 2: Group time slots by date (without time)
const availableSlotsByDate: { [date: string]: { start_time: string; end_time: string }[] } = {};
allTimeSlots.forEach(timeSlot => {
const dateStr = timeSlot.start_time.split('T')[0];
if (dateStr) {
if (!availableSlotsByDate[dateStr]) {
availableSlotsByDate[dateStr] = [];
}
availableSlotsByDate[dateStr].push(timeSlot);
}
});
// Cancel booking
async cancelBooking(bookingId: string): Promise<boolean> {
try {
await pb.collection('bookings').update(bookingId, {
status: 'cancelled',
});
return true;
} catch (error) {
console.error('Error cancelling booking:', error);
return false;
}
// Step 3: Sort time slots within each date
Object.keys(availableSlotsByDate).forEach(date => {
availableSlotsByDate[date]?.sort((a, b) => a.start_time.localeCompare(b.start_time));
});
return availableSlotsByDate;
},
};