init commit

This commit is contained in:
Asya Vee
2025-08-26 16:49:54 +04:00
parent b10285d08b
commit d577f6c608
94 changed files with 27785 additions and 526 deletions

144
apps/web/lib/pocketbase.ts Normal file
View File

@@ -0,0 +1,144 @@
import PocketBase from 'pocketbase';
import { BookingType, Resource, TimeSlot, Booking } from '@/types/booking';
// Initialize PocketBase client
export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL || 'http://127.0.0.1:8090');
// Disable auto cancellation for SSR
pb.autoCancellation(false);
// API functions for booking system
export const bookingApi = {
// Get all active booking types
async getBookingTypes(): Promise<BookingType[]> {
try {
const records = await pb.collection('bookingTypes').getFullList<BookingType>({
sort: 'created',
expand: 'resources',
});
return records;
} catch (error) {
console.error('Error fetching booking types:', error);
throw error;
}
},
// Get available resources
async getResources(): Promise<Resource[]> {
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;
}
},
// 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 records = await pb.collection('timeSlots').getFullList<TimeSlot>({
filter: `is_active = true && booking_types ~ "${bookingTypeId}" && start_time >= "${startDateStr}" && start_time <= "${endDateStr}"`,
sort: 'start_time',
});
return records;
} catch (error) {
console.error('Error fetching time slots:', 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}"`,
});
// Get the time slot to check max capacity
const timeSlot = await pb.collection('timeSlots').getOne<TimeSlot>(timeSlotId);
const currentBookings = existingBookings.reduce((sum, booking) =>
sum + (booking.participants_count || 0), 0
);
const availableSpots = timeSlot.max_capacity - currentBookings;
const available = availableSpots >= participantsCount;
return {
available,
currentBookings,
};
} catch (error) {
console.error('Error checking availability:', error);
throw error;
}
},
// 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;
}
},
// 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;
}
},
// 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;
}
},
};