booking capacity check for time slots

This commit is contained in:
Asya Vee
2025-08-27 02:08:23 +04:00
parent c0647fe512
commit 459289df2d
11 changed files with 255 additions and 13 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,38 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_586073990")
// update field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "number3301820327",
"max": null,
"min": 1,
"name": "max_booking_capacity",
"onlyInt": false,
"presentable": false,
"required": true,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_586073990")
// update field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "number3301820327",
"max": null,
"min": 1,
"name": "max_capacity",
"onlyInt": false,
"presentable": false,
"required": true,
"system": false,
"type": "number"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,66 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_43114331")
// update field
collection.fields.addAt(9, new Field({
"hidden": false,
"id": "number1421793101",
"max": null,
"min": null,
"name": "min_participants_capacity",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
// update field
collection.fields.addAt(10, new Field({
"hidden": false,
"id": "number3301820327",
"max": null,
"min": null,
"name": "max_participants_capacity",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_43114331")
// update field
collection.fields.addAt(9, new Field({
"hidden": false,
"id": "number1421793101",
"max": null,
"min": null,
"name": "min_capacity",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
// update field
collection.fields.addAt(10, new Field({
"hidden": false,
"id": "number3301820327",
"max": null,
"min": null,
"name": "max_capacity",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,27 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_586073990")
// remove field
collection.fields.removeById("number3301820327")
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_586073990")
// add field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "number3301820327",
"max": null,
"min": 1,
"name": "max_booking_capacity",
"onlyInt": false,
"presentable": false,
"required": true,
"system": false,
"type": "number"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,27 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_43114331")
// add field
collection.fields.addAt(12, new Field({
"hidden": false,
"id": "number2396794873",
"max": null,
"min": null,
"name": "booking_capacity",
"onlyInt": false,
"presentable": false,
"required": true,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_43114331")
// remove field
collection.fields.removeById("number2396794873")
return app.save(collection)
})

View File

@@ -0,0 +1,22 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_986407980")
// update collection data
unmarshal({
"listRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_986407980")
// update collection data
unmarshal({
"listRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

View File

@@ -119,21 +119,58 @@ const BookingInterface = () => {
} }
}; };
const handleDateChange = (date: string) => { const handleDateChange = async (date: string) => {
setSelectedDate(date); setSelectedDate(date);
setSelectedTimeSlot(''); setSelectedTimeSlot('');
// Get time slots for the selected date from availableSlotsByDate // Get time slots for the selected date from availableSlotsByDate
const slotsForDate = availableSlotsByDate[date] || []; const slotsForDate = availableSlotsByDate[date] || [];
console.log(`Time slots for ${date}:`, slotsForDate);
// Convert to TimeSlot format for TimeSlotSelector // Get bookings for this date filtered by booking type IDs
const formattedTimeSlots: TimeSlot[] = slotsForDate.map((slot, index) => ({ let bookingOverlapCounts: { [key: string]: number } = {};
try {
const bookings = await bookingApi.getBookingsForDate(date, [selectedBookingType]);
// Count overlapping bookings for each time slot
slotsForDate.forEach(slot => {
const slotStart = new Date(slot.start_time);
const slotEnd = new Date(slot.end_time);
const overlappingBookings = bookings.filter(booking => {
const bookingStart = new Date(booking.start_time);
const bookingEnd = new Date(booking.end_time);
// Check if bookings overlap with time slot
return bookingStart < slotEnd && bookingEnd > slotStart;
});
const totalParticipants = overlappingBookings.reduce((sum, booking) =>
sum + (booking.participants_count || 0), 0
);
const key = `${slot.start_time}-${slot.end_time}`;
bookingOverlapCounts[key] = totalParticipants;
});
console.log('Booking overlap counts:', bookingOverlapCounts);
} catch (error) {
console.error('Error fetching bookings for date:', error);
}
// Convert to TimeSlot format and filter out fully booked slots
const bookingTypeCapacity = selectedBookingTypeData?.booking_capacity || 8;
const availableTimeSlots = slotsForDate.filter(slot => {
const key = `${slot.start_time}-${slot.end_time}`;
const overlappingCount = bookingOverlapCounts[key] || 0;
return overlappingCount < bookingTypeCapacity;
});
const formattedTimeSlots: TimeSlot[] = availableTimeSlots.map((slot, index) => ({
id: `slot-${date}-${index}`, id: `slot-${date}-${index}`,
start_time: slot.start_time, start_time: slot.start_time,
end_time: slot.end_time, end_time: slot.end_time,
is_active: true, is_active: true,
max_capacity: 8, booking_capacity: bookingTypeCapacity,
booking_types: [selectedBookingType], booking_types: [selectedBookingType],
is_reccuring: false, is_reccuring: false,
recurrence_pattern: undefined, recurrence_pattern: undefined,

View File

@@ -35,11 +35,14 @@ const DateSelector: React.FC<DateSelectorProps> = ({
date.setDate(today.getDate() + i); date.setDate(today.getDate() + i);
const dateString = date.toISOString().split('T')[0] || ''; const dateString = date.toISOString().split('T')[0] || '';
// Create date object from the ISO string to avoid timezone issues
const displayDate = new Date(dateString + 'T12:00:00.000Z');
days.push({ days.push({
date: dateString, date: dateString,
day: date.getDate(), day: parseInt(dateString.split('-')[2]),
month: date.toLocaleDateString('en', { month: 'short' }), month: displayDate.toLocaleDateString('en', { month: 'short', timeZone: 'UTC' }),
dayName: date.toLocaleDateString('en', { weekday: 'short' }), dayName: displayDate.toLocaleDateString('en', { weekday: 'short', timeZone: 'UTC' }),
available: availableDates.includes(dateString) available: availableDates.includes(dateString)
}); });
} }

View File

@@ -1,5 +1,5 @@
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
import { BookingType, TimeSlot } from '@/types/bookings'; import { BookingType, TimeSlot, Booking } from '@/types/bookings';
// Initialize PocketBase client // Initialize PocketBase client
export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL || 'http://127.0.0.1:8090'); export const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL || 'http://127.0.0.1:8090');
@@ -122,4 +122,26 @@ export const bookingApi = {
return availableSlotsByDate; return availableSlotsByDate;
}, },
// Get all bookings for a specific date filtered by booking type IDs
async getBookingsForDate(
date: string,
bookingTypeIds: string[]
): Promise<Booking[]> {
try {
// Create filter for booking type IDs
const bookingTypeFilter = bookingTypeIds.map(id => `booking_type = "${id}"`).join(' || ');
const bookings = await pb.collection('bookings').getFullList<Booking>({
filter: `status = "confirmed" && start_time ~ "${date}" && (${bookingTypeFilter})`,
sort: 'start_time'
});
console.log(`Bookings for ${date}:`, bookings);
return bookings;
} catch (error) {
console.error('Error fetching bookings for date:', error);
return [];
}
},
}; };

View File

@@ -9,7 +9,7 @@ export interface BaseRecord {
export interface Resource extends BaseRecord { export interface Resource extends BaseRecord {
name: string; name: string;
type: 'wheel' | 'workstation'; type: 'wheel' | 'workstation';
capacity: number; usage_capacity: number;
is_active: boolean; is_active: boolean;
} }
@@ -23,9 +23,10 @@ export interface BookingType extends BaseRecord {
min_duration: number; min_duration: number;
price_per_person: number; price_per_person: number;
resources: string; // relation to Resource resources: string; // relation to Resource
min_capacity: number; min_participants_capacity: number;
max_capacity: number; max_participants_capacity: number;
is_active: boolean; is_active: boolean;
booking_capacity: number;
} }
// Time Slot entity // Time Slot entity
@@ -36,7 +37,6 @@ export interface TimeSlot extends BaseRecord {
is_active: boolean; is_active: boolean;
is_reccuring?: boolean; is_reccuring?: boolean;
recurrence_pattern?: RecurrencePattern; recurrence_pattern?: RecurrencePattern;
max_capacity: number;
} }
// Recurrence pattern structure // Recurrence pattern structure